You've already forked SmartisanNote.Remake
future #13
@@ -88,6 +88,10 @@
|
|||||||
--white-60: #ffffff99; /* 60% white */
|
--white-60: #ffffff99; /* 60% white */
|
||||||
--white-80: #ffffffcc; /* 80% white */
|
--white-80: #ffffffcc; /* 80% white */
|
||||||
--white-90: #ffffffe6; /* 90% white */
|
--white-90: #ffffffe6; /* 90% white */
|
||||||
|
|
||||||
|
--confirmFontSize: 0.8rem;
|
||||||
|
--confirmBg: rgba(0, 0, 0, 0.15);
|
||||||
|
--confirmBtnColor: #000000cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"vue": "^3.5.22",
|
"vue": "^3.5.22",
|
||||||
"vue-draggable-plus": "^0.6.0",
|
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
20
src/App.vue
20
src/App.vue
@@ -13,19 +13,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 设置页面 -->
|
<!-- 设置页面 -->
|
||||||
<transition
|
<transition name="settings-slide" v-show="isSettingsRoute" appear>
|
||||||
name="settings-slide"
|
|
||||||
v-show="isSettingsRoute"
|
|
||||||
appear>
|
|
||||||
<SettingsPage class="setting-page" />
|
<SettingsPage class="setting-page" />
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, computed } 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 { initModalService } from '@/utils/modalService'
|
||||||
|
|
||||||
// 导入页面组件
|
// 导入页面组件
|
||||||
import NoteListPage from './pages/NoteListPage.vue'
|
import NoteListPage from './pages/NoteListPage.vue'
|
||||||
@@ -33,6 +31,7 @@ import SettingsPage from './pages/SettingsPage.vue'
|
|||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const transitionName = ref('slide-left')
|
const transitionName = ref('slide-left')
|
||||||
|
const modalRef = ref()
|
||||||
|
|
||||||
// 计算是否为设置页面路由
|
// 计算是否为设置页面路由
|
||||||
const isSettingsRoute = computed(() => {
|
const isSettingsRoute = computed(() => {
|
||||||
@@ -53,15 +52,15 @@ watch(
|
|||||||
transitionName.value = 'slide-right'
|
transitionName.value = 'slide-right'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断导航方向
|
// 判断导航方向
|
||||||
const toDepth = toPath.split('/').length
|
const toDepth = toPath.split('/').length
|
||||||
const fromDepth = fromPath.split('/').length
|
const fromDepth = fromPath.split('/').length
|
||||||
|
|
||||||
// 如果是进入更深的页面(如从列表页进入编辑页),使用左滑动画
|
// 如果是进入更深的页面(如从列表页进入编辑页),使用左滑动画
|
||||||
if (toDepth > fromDepth) {
|
if (toDepth > fromDepth) {
|
||||||
transitionName.value = 'slide-left'
|
transitionName.value = 'slide-left'
|
||||||
}
|
}
|
||||||
// 如果是返回上层页面(如从编辑页返回列表页),使用右滑动画
|
// 如果是返回上层页面(如从编辑页返回列表页),使用右滑动画
|
||||||
else if (toDepth < fromDepth) {
|
else if (toDepth < fromDepth) {
|
||||||
transitionName.value = 'slide-right'
|
transitionName.value = 'slide-right'
|
||||||
@@ -73,7 +72,10 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 无额外处理函数
|
// 初始化弹框服务
|
||||||
|
onMounted(() => {
|
||||||
|
initModalService()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@@ -1,77 +1,168 @@
|
|||||||
<template>
|
<template>
|
||||||
<div @click="onPress" class="code-fun-flex-row code-fun-items-center code-fun-relative folder-item">
|
<div @click="onPress" class="code-fun-flex-row code-fun-items-center code-fun-relative code-fun-justify-between folder-item">
|
||||||
<img class="folder-icon" :src="iconSrc" />
|
<div class="code-fun-flex-row code-fun-items-center">
|
||||||
<span class="folder-name">{{ name }}</span>
|
<!-- 文件夹图标或复选框 -->
|
||||||
<span class="folder-count">{{ noteCount }}</span>
|
<div v-if="isSelectionMode && showDeleteButton" class="folder-checkbox" @click.stop="onCheckboxClick">
|
||||||
</div>
|
<img :src="isChecked ? '/assets/icons/drawable-xxhdpi/check_box_on.png' : '/assets/icons/drawable-xxhdpi/check_box_off.png'" class="checkbox-icon" />
|
||||||
</template>
|
</div>
|
||||||
|
|
||||||
<script setup>
|
<img v-else class="folder-icon" :src="iconSrc" />
|
||||||
import { computed } from 'vue'
|
<span class="folder-name">{{ name }}</span>
|
||||||
|
<span class="folder-count">{{ noteCount }}</span>
|
||||||
const props = defineProps({
|
</div>
|
||||||
id: {
|
|
||||||
type: String,
|
<!-- 编辑按钮,仅在选择模式下对自定义文件夹显示 -->
|
||||||
required: true,
|
<div v-if="showDeleteButton && isSelectionMode" class="folder-actions">
|
||||||
},
|
<img class="edit-icon" src="/assets/icons/drawable-xxhdpi/icon_folder_rename.png" @click.stop="onEdit" />
|
||||||
name: {
|
</div>
|
||||||
type: String,
|
</div>
|
||||||
required: true,
|
</template>
|
||||||
},
|
|
||||||
noteCount: {
|
<script setup>
|
||||||
type: Number,
|
import { computed } from 'vue'
|
||||||
required: true,
|
|
||||||
},
|
const props = defineProps({
|
||||||
onPress: {
|
id: {
|
||||||
type: Function,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
isSelected: {
|
|
||||||
type: Boolean,
|
name: {
|
||||||
default: false,
|
type: String,
|
||||||
},
|
required: true,
|
||||||
})
|
},
|
||||||
|
|
||||||
const iconSrc = computed(() => {
|
noteCount: {
|
||||||
switch (props.id) {
|
type: Number,
|
||||||
case 'all':
|
required: true,
|
||||||
return 'assets/icons/drawable-xxhdpi/icon_folder_all.png'
|
},
|
||||||
case 'starred':
|
|
||||||
return 'assets/icons/drawable-xxhdpi/icon_folder_favorite.png'
|
onPress: {
|
||||||
case 'trash':
|
type: Function,
|
||||||
return 'assets/icons/drawable-xxhdpi/icon_folder_trash.png'
|
required: true,
|
||||||
case 'archive':
|
},
|
||||||
return 'assets/icons/drawable-xxhdpi/icon_folder_document.png'
|
|
||||||
default:
|
onEdit: {
|
||||||
return 'assets/icons/drawable-xxhdpi/icon_folder_document.png'
|
type: Function,
|
||||||
}
|
default: null,
|
||||||
})
|
},
|
||||||
</script>
|
|
||||||
|
onDelete: {
|
||||||
<style scoped>
|
type: Function,
|
||||||
.folder-item {
|
default: null,
|
||||||
padding: 0.3rem 0;
|
},
|
||||||
background-color: #00000000;
|
|
||||||
}
|
isSelected: {
|
||||||
|
type: Boolean,
|
||||||
.folder-icon {
|
default: false,
|
||||||
width: 1.8rem;
|
},
|
||||||
height: 1.8rem;
|
|
||||||
flex-shrink: 0;
|
isSelectionMode: {
|
||||||
margin-inline: 0.3rem;
|
type: Boolean,
|
||||||
}
|
|
||||||
|
default: false,
|
||||||
.folder-name {
|
},
|
||||||
font-size: 0.9rem;
|
|
||||||
line-height: 1.52rem;
|
isChecked: {
|
||||||
color: #9b9b9b;
|
type: Boolean,
|
||||||
}
|
|
||||||
|
default: false,
|
||||||
.folder-count {
|
},
|
||||||
font-size: 0.8rem;
|
|
||||||
line-height: 1.16rem;
|
onCheckboxClick: {
|
||||||
margin-left: 0.7rem;
|
type: Function,
|
||||||
margin-top: 0.2rem;
|
|
||||||
color: #b8b8b8;
|
default: null,
|
||||||
}
|
},
|
||||||
</style>
|
})
|
||||||
|
|
||||||
|
const iconSrc = computed(() => {
|
||||||
|
switch (props.id) {
|
||||||
|
case 'all':
|
||||||
|
return 'assets/icons/drawable-xxhdpi/icon_folder_all.png'
|
||||||
|
|
||||||
|
case 'starred':
|
||||||
|
return 'assets/icons/drawable-xxhdpi/icon_folder_favorite.png'
|
||||||
|
|
||||||
|
case 'trash':
|
||||||
|
return 'assets/icons/drawable-xxhdpi/icon_folder_trash.png'
|
||||||
|
|
||||||
|
case 'archive':
|
||||||
|
return 'assets/icons/drawable-xxhdpi/icon_folder_document.png'
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 'assets/icons/drawable-xxhdpi/icon_folder_document.png'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 仅对自定义文件夹显示删除按钮
|
||||||
|
|
||||||
|
const showDeleteButton = computed(() => {
|
||||||
|
return !['all', 'starred', 'trash', 'archive'].includes(props.id)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.folder-item {
|
||||||
|
padding: 0.3rem 0;
|
||||||
|
background-color: #00000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-icon {
|
||||||
|
width: 1.8rem;
|
||||||
|
height: 1.8rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-inline: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-name {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.52rem;
|
||||||
|
color: #9b9b9b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-count {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.16rem;
|
||||||
|
margin-left: 0.7rem;
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
color: #b8b8b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-actions {
|
||||||
|
display: flex;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-icon,
|
||||||
|
.delete-icon {
|
||||||
|
width: 1.5rem;
|
||||||
|
|
||||||
|
height: 1.5rem;
|
||||||
|
|
||||||
|
margin: 0 0.2rem;
|
||||||
|
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-icon:hover,
|
||||||
|
.delete-icon:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.8rem;
|
||||||
|
height: 1.8rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-inline: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-icon {
|
||||||
|
width: 70%;
|
||||||
|
height: 70%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,135 +1,335 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="code-fun-flex-col page">
|
<div class="code-fun-flex-col page">
|
||||||
<!-- 全部便签文件夹项 -->
|
<!-- 全部便签文件夹项 -->
|
||||||
<FolderItem id="all" name="全部便签" :noteCount="allCount" :isSelected="selectedFolder === 'all'" :onPress="handleAllClick" />
|
|
||||||
|
<FolderItem id="all" name="全部便签" :noteCount="allCount" :isSelected="selectedFolder === 'all'" :onPress="handleAllClick" />
|
||||||
<!-- 加星便签文件夹项 -->
|
|
||||||
<FolderItem id="starred" name="加星便签" :noteCount="starredCount" :isSelected="selectedFolder === 'starred'" :onPress="handleStarredClick" />
|
<!-- 加星便签文件夹项 -->
|
||||||
|
|
||||||
<!-- 回收站文件夹项 -->
|
<FolderItem id="starred" name="加星便签" :noteCount="starredCount" :isSelected="selectedFolder === 'starred'" :onPress="handleStarredClick" />
|
||||||
<FolderItem id="trash" name="回收站" :noteCount="trashCount" :isSelected="selectedFolder === 'trash'" :onPress="handleTrashClick" />
|
|
||||||
|
<!-- 回收站文件夹项 -->
|
||||||
<!-- 同步信息区域 -->
|
|
||||||
<div class="code-fun-flex-col code-fun-justify-start section_7">
|
<FolderItem id="trash" name="回收站" :noteCount="trashCount" :isSelected="selectedFolder === 'trash'" :onPress="handleTrashClick" />
|
||||||
<div class="code-fun-flex-row code-fun-justify-between code-fun-items-center section_8">
|
|
||||||
<div class="code-fun-flex-row code-fun-items-center">
|
<!-- 自定义文件夹项 -->
|
||||||
<img class="code-fun-shrink-0 image_7" :src="`assets/icons/drawable-xxhdpi/btn_edit_folder.png`" />
|
|
||||||
<span class="text_11 code-fun-ml-10">上次同步:{{ lastSyncTime }}</span>
|
<FolderItem
|
||||||
</div>
|
v-for="folder in customFolders"
|
||||||
<div class="code-fun-flex-row code-fun-items-center">
|
:key="folder.id"
|
||||||
<img class="image_8 code-fun-ml-12" :src="`assets/icons/drawable-xxhdpi/btn_add_folder.png`" @click="handleAddFolder" />
|
:id="folder.id"
|
||||||
</div>
|
:name="folder.name"
|
||||||
</div>
|
:noteCount="getFolderNoteCount(folder.id)"
|
||||||
</div>
|
:isSelected="selectedFolder === folder.id"
|
||||||
</div>
|
:onPress="() => handleFolderClick(folder.id)"
|
||||||
</template>
|
:onEdit="() => handleEditFolder(folder.id)"
|
||||||
|
:onDelete="() => handleDeleteFolder(folder.id)"
|
||||||
<script setup>
|
:isSelectionMode="isSelectionMode"
|
||||||
import { computed } from 'vue'
|
:isChecked="selectedFolders.includes(folder.id)"
|
||||||
import FolderItem from './FolderItem.vue'
|
:onCheckboxClick="() => toggleFolderSelection(folder.id)" />
|
||||||
|
|
||||||
const props = defineProps({
|
<div class="code-fun-flex-col code-fun-justify-start section_7">
|
||||||
allCount: {
|
<div class="code-fun-flex-row code-fun-justify-between code-fun-items-center section_8">
|
||||||
type: Number,
|
<div class="code-fun-flex-row code-fun-items-center">
|
||||||
default: 0,
|
<!-- 切换文件夹选择模式按钮 -->
|
||||||
},
|
|
||||||
starredCount: {
|
<img class="code-fun-shrink-0 image_7" :src="isSelectionMode ? 'assets/icons/drawable-xxhdpi/btn_back_black.png' : 'assets/icons/drawable-xxhdpi/btn_edit_folder.png'" @click="toggleSelectionMode" />
|
||||||
type: Number,
|
|
||||||
default: 0,
|
<!-- 同步信息区域 -->
|
||||||
},
|
|
||||||
trashCount: {
|
<span class="text_11 code-fun-ml-10">上次同步:{{ lastSyncTime }}</span>
|
||||||
type: Number,
|
</div>
|
||||||
default: 0,
|
|
||||||
},
|
<div class="code-fun-flex-row code-fun-items-center">
|
||||||
archiveCount: {
|
<!-- 新建文件夹按钮 / 删除按钮 -->
|
||||||
type: Number,
|
|
||||||
default: 0,
|
<img
|
||||||
},
|
class="image_8 code-fun-ml-12"
|
||||||
selectedFolder: {
|
:src="isSelectionMode ? 'assets/icons/drawable-xxhdpi/icon_folder_trash.png' : 'assets/icons/drawable-xxhdpi/btn_add_folder.png'"
|
||||||
type: String,
|
@click="isSelectionMode ? handleDeleteSelectedFolders() : handleAddFolder()" />
|
||||||
default: '',
|
</div>
|
||||||
},
|
</div>
|
||||||
lastSyncTime: {
|
</div>
|
||||||
type: String,
|
</div>
|
||||||
default: '10/10上午9:28',
|
</template>
|
||||||
},
|
|
||||||
onAllClick: {
|
<script setup>
|
||||||
type: Function,
|
import { ref, computed, nextTick } from 'vue'
|
||||||
default: null,
|
import { useAppStore } from '../stores/useAppStore'
|
||||||
},
|
import FolderItem from './FolderItem.vue'
|
||||||
onStarredClick: {
|
import { showConfirm, showPrompt } from '../utils/modalService'
|
||||||
type: Function,
|
|
||||||
default: null,
|
const store = useAppStore()
|
||||||
},
|
const props = defineProps({
|
||||||
onTrashClick: {
|
allCount: {
|
||||||
type: Function,
|
type: Number,
|
||||||
default: null,
|
default: 0,
|
||||||
},
|
},
|
||||||
onArchiveClick: {
|
starredCount: {
|
||||||
type: Function,
|
type: Number,
|
||||||
default: null,
|
default: 0,
|
||||||
},
|
},
|
||||||
onAddFolder: {
|
trashCount: {
|
||||||
type: Function,
|
type: Number,
|
||||||
default: null,
|
default: 0,
|
||||||
},
|
},
|
||||||
})
|
archiveCount: {
|
||||||
|
type: Number,
|
||||||
const handleAllClick = () => {
|
default: 0,
|
||||||
if (props.onAllClick) {
|
},
|
||||||
props.onAllClick()
|
selectedFolder: {
|
||||||
}
|
type: String,
|
||||||
}
|
default: '',
|
||||||
|
},
|
||||||
const handleStarredClick = () => {
|
lastSyncTime: {
|
||||||
if (props.onStarredClick) {
|
type: String,
|
||||||
props.onStarredClick()
|
default: '10/10上午9:28',
|
||||||
}
|
},
|
||||||
}
|
onAllClick: {
|
||||||
|
type: Function,
|
||||||
const handleTrashClick = () => {
|
default: null,
|
||||||
if (props.onTrashClick) {
|
},
|
||||||
props.onTrashClick()
|
onStarredClick: {
|
||||||
}
|
type: Function,
|
||||||
}
|
default: null,
|
||||||
|
},
|
||||||
const handleArchiveClick = () => {
|
onTrashClick: {
|
||||||
if (props.onArchiveClick) {
|
type: Function,
|
||||||
props.onArchiveClick()
|
default: null,
|
||||||
}
|
},
|
||||||
}
|
onArchiveClick: {
|
||||||
|
type: Function,
|
||||||
const handleAddFolder = event => {
|
default: null,
|
||||||
// 阻止事件冒泡到父元素
|
},
|
||||||
event.stopPropagation()
|
onAddFolder: {
|
||||||
if (props.onAddFolder) {
|
type: Function,
|
||||||
props.onAddFolder()
|
default: null,
|
||||||
}
|
},
|
||||||
}
|
onFolderClick: {
|
||||||
</script>
|
type: Function,
|
||||||
|
default: null,
|
||||||
<style lang="less" scoped>
|
},
|
||||||
.page {
|
})
|
||||||
.section_7 {
|
|
||||||
margin-top: 2rem;
|
// 重命名文件夹相关状态
|
||||||
background-color: #00000000;
|
|
||||||
.section_8 {
|
const showRenameFolderModal = ref(false)
|
||||||
padding: 0.29rem 0.92rem;
|
|
||||||
background-color: #f4f4f4;
|
const renameFolderId = ref(null)
|
||||||
border: solid 0.063rem #f0ece7;
|
|
||||||
.image_7,
|
const renameFolderName = ref('')
|
||||||
.image_8 {
|
// 选择模式相关状态
|
||||||
border-radius: 0.63rem;
|
|
||||||
width: 2rem;
|
const isSelectionMode = ref(false)
|
||||||
height: 2rem;
|
|
||||||
object-fit: contain;
|
const selectedFolders = ref([])
|
||||||
}
|
// 计算自定义文件夹(排除系统文件夹)
|
||||||
.text_11 {
|
const customFolders = computed(() => {
|
||||||
color: #cacaca;
|
return store.folders.filter(folder => !['all', 'starred', 'trash', 'archive'].includes(folder.id))
|
||||||
font-size: 0.7rem;
|
})
|
||||||
line-height: 1.16rem;
|
|
||||||
}
|
// 获取文件夹中的便签数量
|
||||||
}
|
const getFolderNoteCount = folderId => {
|
||||||
}
|
return store.notes.filter(note => note.folderId === folderId && !note.isDeleted).length
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
// 切换选择模式
|
||||||
|
|
||||||
|
const toggleSelectionMode = () => {
|
||||||
|
isSelectionMode.value = !isSelectionMode.value
|
||||||
|
// 退出选择模式时清空选中项
|
||||||
|
if (!isSelectionMode.value) {
|
||||||
|
selectedFolders.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换文件夹选中状态
|
||||||
|
const toggleFolderSelection = folderId => {
|
||||||
|
if (!isSelectionMode.value) return
|
||||||
|
const index = selectedFolders.value.indexOf(folderId)
|
||||||
|
if (index > -1) {
|
||||||
|
// 已选中,取消选中
|
||||||
|
selectedFolders.value.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
// 未选中,添加选中
|
||||||
|
selectedFolders.value.push(folderId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文件夹点击
|
||||||
|
|
||||||
|
const handleFolderClick = folderId => {
|
||||||
|
if (isSelectionMode.value) {
|
||||||
|
toggleFolderSelection(folderId)
|
||||||
|
} else {
|
||||||
|
if (props.onFolderClick) {
|
||||||
|
props.onFolderClick(folderId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理删除选中的文件夹
|
||||||
|
|
||||||
|
const handleDeleteSelectedFolders = async () => {
|
||||||
|
if (selectedFolders.value.length === 0) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const confirmed = await showConfirm(`确定要删除选中的 ${selectedFolders.value.length} 个文件夹吗?文件夹中的便签将移至"全部便签"。`, '删除文件夹')
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
|
// 删除选中的文件夹
|
||||||
|
|
||||||
|
for (const folderId of selectedFolders.value) {
|
||||||
|
// 跳过系统文件夹
|
||||||
|
|
||||||
|
if (['all', 'starred', 'trash', 'archive'].includes(folderId)) continue
|
||||||
|
|
||||||
|
await store.deleteFolder(folderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空选中项并退出选择模式
|
||||||
|
|
||||||
|
selectedFolders.value = []
|
||||||
|
|
||||||
|
isSelectionMode.value = false
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除文件夹失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理删除文件夹
|
||||||
|
|
||||||
|
const handleDeleteFolder = async folderId => {
|
||||||
|
// 阻止事件冒泡到父元素
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
// 确认删除
|
||||||
|
try {
|
||||||
|
const confirmed = await showConfirm(`确定要删除文件夹 "${getFolderName(folderId)}" 吗?文件夹中的便签将移至"全部便签"。`, '删除文件夹')
|
||||||
|
if (confirmed) {
|
||||||
|
await store.deleteFolder(folderId)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除文件夹失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理编辑文件夹
|
||||||
|
|
||||||
|
const handleEditFolder = async folderId => {
|
||||||
|
// 阻止事件冒泡到父元素
|
||||||
|
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
const folder = store.folders.find(f => f.id === folderId)
|
||||||
|
|
||||||
|
if (folder) {
|
||||||
|
try {
|
||||||
|
const newName = await showPrompt('请输入文件夹名称', '重命名文件夹', '请输入文件夹名称', folder.name)
|
||||||
|
|
||||||
|
if (newName && newName.trim()) {
|
||||||
|
await store.updateFolder(folderId, { name: newName.trim() })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 用户取消操作或出现错误
|
||||||
|
|
||||||
|
console.log('重命名文件夹操作已取消或出现错误:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件夹名称
|
||||||
|
|
||||||
|
const getFolderName = folderId => {
|
||||||
|
const folder = store.folders.find(f => f.id === folderId)
|
||||||
|
return folder ? folder.name : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAllClick = () => {
|
||||||
|
if (isSelectionMode.value) {
|
||||||
|
toggleFolderSelection('all')
|
||||||
|
} else {
|
||||||
|
if (props.onAllClick) {
|
||||||
|
props.onAllClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStarredClick = () => {
|
||||||
|
if (isSelectionMode.value) {
|
||||||
|
toggleFolderSelection('starred')
|
||||||
|
} else {
|
||||||
|
if (props.onStarredClick) {
|
||||||
|
props.onStarredClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTrashClick = () => {
|
||||||
|
if (isSelectionMode.value) {
|
||||||
|
toggleFolderSelection('trash')
|
||||||
|
} else {
|
||||||
|
if (props.onTrashClick) {
|
||||||
|
props.onTrashClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddFolder = async () => {
|
||||||
|
try {
|
||||||
|
const folderName = await showPrompt('请输入文件夹名称', '添加文件夹', '请输入文件夹名称')
|
||||||
|
if (folderName && folderName.trim()) {
|
||||||
|
const newFolder = {
|
||||||
|
name: folderName.trim(),
|
||||||
|
id: `folder_${Date.now()}`, // 生成唯一ID
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.addFolder(newFolder)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 用户取消操作或出现错误
|
||||||
|
console.log('添加文件夹操作已取消或出现错误:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.page {
|
||||||
|
.section_7 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
|
||||||
|
background-color: #00000000;
|
||||||
|
|
||||||
|
.section_8 {
|
||||||
|
padding: 0.29rem 0.92rem;
|
||||||
|
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
|
||||||
|
border: solid 0.063rem #f0ece7;
|
||||||
|
|
||||||
|
.image_7,
|
||||||
|
.image_8 {
|
||||||
|
border-radius: 0.63rem;
|
||||||
|
|
||||||
|
width: 2.2rem;
|
||||||
|
|
||||||
|
height: 1.75rem;
|
||||||
|
|
||||||
|
object-fit: contain;
|
||||||
|
|
||||||
|
background: url(/assets/icons/drawable-xxhdpi/folder_bottom_button_normal.9.png), #fdfbfb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text_11 {
|
||||||
|
color: #cacaca;
|
||||||
|
|
||||||
|
font-size: 0.7rem;
|
||||||
|
|
||||||
|
line-height: 1.16rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,206 +1,347 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="code-fun-flex-col code-fun-justify-start">
|
<div v-if="visible" class="pd-mask" @click="handleMaskClick">
|
||||||
<div class="code-fun-flex-col code-fun-justify-start group_1">
|
<div class="pd-confirm" @click.stop>
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-items-end group">
|
<h2 class="pd-title">{{ dynamicTitle || title }}</h2>
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-items-start code-fun-relative group_2">
|
|
||||||
<div class="code-fun-flex-col section_7">
|
<div class="pd-input-container" v-if="dynamicShowInput || showInput">
|
||||||
<div class="code-fun-flex-col code-fun-justify-start section_1">
|
<input ref="inputRef" v-model="inputModel" class="pd-input" :placeholder="dynamicInputPlaceholder || inputPlaceholder" @keyup.enter="handleConfirm" />
|
||||||
<div class="code-fun-flex-row section_11">
|
</div>
|
||||||
<div class="code-fun-flex-col code-fun-justify-start section_12" @click="handleCancel">
|
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-items-center text-wrapper_2">
|
<p class="pd-message" v-else>{{ dynamicMessage || message }}</p>
|
||||||
<span class="text_4">{{ cancelText }}</span>
|
|
||||||
</div>
|
<div class="pd-buttons">
|
||||||
</div>
|
<button v-if="dynamicShowConfirm || showConfirm" class="pd-button pd-confirm-btn" @click="handleConfirm">
|
||||||
<div class="code-fun-flex-col code-fun-justify-start section_13 code-fun-ml-8" @click="handleConfirm">
|
{{ dynamicConfirmText || confirmText }}
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-items-center text-wrapper_3">
|
</button>
|
||||||
<span class="text_5">{{ confirmText }}</span>
|
|
||||||
</div>
|
<button v-if="dynamicShowCancel || showCancel" class="pd-button pd-cancel" @click="handleCancel">
|
||||||
</div>
|
{{ dynamicCancelText || cancelText }}
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-relative section_9">
|
</div>
|
||||||
<div class="code-fun-flex-col code-fun-justify-start section_10">
|
</div>
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-items-start text-wrapper">
|
</template>
|
||||||
<input
|
|
||||||
v-model="inputValue"
|
<script setup>
|
||||||
class="text_3"
|
import { ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
||||||
:placeholder="placeholder"
|
|
||||||
@keyup.enter="handleConfirm"
|
// 响应式数据用于动态更新 Modal 内容
|
||||||
/>
|
|
||||||
</div>
|
const dynamicTitle = ref('')
|
||||||
</div>
|
|
||||||
</div>
|
const dynamicMessage = ref('')
|
||||||
</div>
|
|
||||||
<div class="code-fun-flex-col section_8 pos">
|
const dynamicConfirmText = ref('确认')
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-items-start code-fun-self-stretch group_3">
|
|
||||||
<span class="text_2">{{ title }}</span>
|
const dynamicCancelText = ref('取消')
|
||||||
</div>
|
|
||||||
<div class="code-fun-self-start divider"></div>
|
const dynamicShowConfirm = ref(true)
|
||||||
</div>
|
|
||||||
</div>
|
const dynamicShowCancel = ref(true)
|
||||||
</div>
|
|
||||||
</div>
|
const dynamicMaskClosable = ref(false)
|
||||||
</div>
|
|
||||||
</template>
|
const dynamicShowInput = ref(false)
|
||||||
|
|
||||||
<script setup>
|
const dynamicInputPlaceholder = ref('请输入文字')
|
||||||
import { ref } from 'vue';
|
|
||||||
|
const props = defineProps({
|
||||||
const props = defineProps({
|
title: {
|
||||||
title: {
|
type: String,
|
||||||
type: String,
|
default: '',
|
||||||
default: '新建文件夹'
|
},
|
||||||
},
|
message: {
|
||||||
placeholder: {
|
type: String,
|
||||||
type: String,
|
default: '',
|
||||||
default: '新建文件夹'
|
},
|
||||||
},
|
confirmText: {
|
||||||
confirmText: {
|
type: String,
|
||||||
type: String,
|
default: '确认',
|
||||||
default: '确定'
|
},
|
||||||
},
|
cancelText: {
|
||||||
cancelText: {
|
type: String,
|
||||||
type: String,
|
default: '取消',
|
||||||
default: '取消'
|
},
|
||||||
}
|
showConfirm: {
|
||||||
});
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
const emit = defineEmits(['confirm', 'cancel']);
|
},
|
||||||
|
showCancel: {
|
||||||
const inputValue = ref('');
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
const handleConfirm = () => {
|
},
|
||||||
emit('confirm', inputValue.value);
|
maskClosable: {
|
||||||
};
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
const handleCancel = () => {
|
},
|
||||||
emit('cancel');
|
showInput: {
|
||||||
};
|
type: Boolean,
|
||||||
</script>
|
default: false,
|
||||||
|
},
|
||||||
<style scoped lang="less">
|
inputPlaceholder: {
|
||||||
.group_1 {
|
type: String,
|
||||||
padding-bottom: 1rem;
|
default: '请输入文字',
|
||||||
.group {
|
},
|
||||||
padding: 0.5rem 0 0.25rem;
|
inputValue: {
|
||||||
.group_2 {
|
type: String,
|
||||||
margin-right: 0.75rem;
|
default: '',
|
||||||
width: 32.31rem;
|
},
|
||||||
.section_7 {
|
})
|
||||||
padding-top: 5.75rem;
|
|
||||||
background-color: #00000000;
|
const emit = defineEmits(['confirm', 'cancel', 'update:visible'])
|
||||||
width: 29.06rem;
|
const visible = defineModel('visible', { type: Boolean, default: false })
|
||||||
height: 18.16rem;
|
const inputModel = defineModel('inputValue', { type: String, default: '' })
|
||||||
background-image: url(https://codefun-proj-user-res-1256085488.cos.ap-guangzhou.myqcloud.com/686f20ecd54496f19f54e801/68e862ab9520a30011f388ff/17600601480475170758.png);
|
|
||||||
background-repeat: no-repeat;
|
const inputRef = ref()
|
||||||
background-size: 100% auto;
|
|
||||||
background-position: 0% 0%;
|
// Promise 控制变量
|
||||||
.section_1 {
|
|
||||||
margin-top: 7.88rem;
|
let resolvePromise, rejectPromise
|
||||||
background-color: #00000000;
|
|
||||||
.section_11 {
|
// 返回 Promise 的方法,模拟原生 confirm 行为
|
||||||
padding: 0.5rem 0.5rem 0.75rem;
|
|
||||||
background-color: #f4f4f7;
|
const show = (options = {}) => {
|
||||||
border-radius: 0rem 0rem 0.75rem 0.75rem;
|
// 更新动态数据
|
||||||
border: solid 0.032rem #edeee8;
|
|
||||||
.section_12 {
|
if (options.title !== undefined) dynamicTitle.value = options.title
|
||||||
padding: 0.25rem 0;
|
|
||||||
background-color: #00000000;
|
if (options.message !== undefined) dynamicMessage.value = options.message
|
||||||
width: 13.63rem;
|
|
||||||
height: 3.13rem;
|
if (options.confirmText !== undefined) dynamicConfirmText.value = options.confirmText
|
||||||
.text-wrapper_2 {
|
|
||||||
margin: 0 0.25rem;
|
if (options.cancelText !== undefined) dynamicCancelText.value = options.cancelText
|
||||||
padding: 0.75rem 0;
|
|
||||||
background-color: #f2f2f2;
|
if (options.showConfirm !== undefined) dynamicShowConfirm.value = options.showConfirm
|
||||||
border-radius: 0.25rem;
|
|
||||||
width: 13.34rem;
|
if (options.showCancel !== undefined) dynamicShowCancel.value = options.showCancel
|
||||||
border: solid 0.032rem #d7d7d7;
|
|
||||||
.text_4 {
|
if (options.maskClosable !== undefined) dynamicMaskClosable.value = options.maskClosable
|
||||||
color: #757575;
|
|
||||||
font-size: 1.23rem;
|
if (options.showInput !== undefined) dynamicShowInput.value = options.showInput
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.23rem;
|
if (options.inputPlaceholder !== undefined) dynamicInputPlaceholder.value = options.inputPlaceholder
|
||||||
}
|
|
||||||
}
|
if (options.inputValue !== undefined) inputModel.value = options.inputValue
|
||||||
}
|
|
||||||
.section_13 {
|
// 显示对话框
|
||||||
margin-right: 0.25rem;
|
|
||||||
padding-top: 0.25rem;
|
visible.value = true
|
||||||
background-color: #00000000;
|
|
||||||
width: 13.59rem;
|
emit('update:visible', true)
|
||||||
height: 3.06rem;
|
|
||||||
.text-wrapper_3 {
|
// 返回 Promise
|
||||||
margin-left: 0.25rem;
|
|
||||||
padding: 0.75rem 0;
|
return new Promise((resolve, reject) => {
|
||||||
border-radius: 0.25rem;
|
resolvePromise = resolve
|
||||||
background-image: url('assets/53062683132af1946e1a4953530af228.png');
|
|
||||||
background-size: 100% 100%;
|
rejectPromise = reject
|
||||||
background-repeat: no-repeat;
|
})
|
||||||
width: 13.34rem;
|
}
|
||||||
.text_5 {
|
|
||||||
color: #cddff2;
|
const handleConfirm = () => {
|
||||||
font-size: 1.19rem;
|
emit('confirm')
|
||||||
line-height: 1.19rem;
|
visible.value = false
|
||||||
}
|
emit('update:visible', false)
|
||||||
}
|
|
||||||
}
|
// 解决 Promise,传递输入框的值
|
||||||
}
|
if (resolvePromise) {
|
||||||
}
|
resolvePromise(inputModel.value)
|
||||||
.section_9 {
|
resolvePromise = null
|
||||||
margin-top: -12.5rem;
|
rejectPromise = null
|
||||||
padding: 1.75rem 0 0.25rem;
|
}
|
||||||
background-color: #00000000;
|
}
|
||||||
.section_10 {
|
|
||||||
margin: 0 1.5rem;
|
const handleCancel = () => {
|
||||||
padding-top: 0.25rem;
|
emit('cancel')
|
||||||
background-color: #00000000;
|
visible.value = false
|
||||||
width: 25.88rem;
|
emit('update:visible', false)
|
||||||
.text-wrapper {
|
|
||||||
margin: 0 0.25rem;
|
// 拒绝 Promise
|
||||||
padding: 0.75rem 0;
|
if (rejectPromise) {
|
||||||
background-color: #fefefe;
|
rejectPromise()
|
||||||
border-radius: 0.25rem;
|
resolvePromise = null
|
||||||
width: 25.53rem;
|
rejectPromise = null
|
||||||
border: solid 0.063rem #e1e1e1;
|
}
|
||||||
.text_3 {
|
}
|
||||||
margin-left: 1rem;
|
|
||||||
color: #d0d0d0;
|
const handleMaskClick = () => {
|
||||||
font-size: 1.38rem;
|
if (props.maskClosable) {
|
||||||
font-weight: 700;
|
handleCancel() // 点击遮罩相当于取消
|
||||||
line-height: 1.38rem;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
// 添加/移除 body 滚动锁定
|
||||||
}
|
const lockBodyScroll = () => {
|
||||||
}
|
document.body.style.overflow = 'hidden'
|
||||||
.section_8 {
|
}
|
||||||
padding: 0 0.13rem;
|
|
||||||
background-color: #00000000;
|
const unlockBodyScroll = () => {
|
||||||
width: 32.31rem;
|
document.body.style.overflow = ''
|
||||||
.group_3 {
|
}
|
||||||
padding: 1.5rem 0;
|
|
||||||
width: 32.06rem;
|
onMounted(() => {
|
||||||
.text_2 {
|
if (visible.value) {
|
||||||
margin-left: 10.5rem;
|
lockBodyScroll()
|
||||||
color: #7a7a7a;
|
}
|
||||||
font-size: 1.54rem;
|
})
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.54rem;
|
onUnmounted(() => {
|
||||||
}
|
unlockBodyScroll()
|
||||||
}
|
})
|
||||||
.divider {
|
|
||||||
background-color: #f0f0f0;
|
// 监听 visible 变化
|
||||||
width: 29rem;
|
watch(visible, (newVal, oldVal) => {
|
||||||
height: 0.094rem;
|
if (newVal) {
|
||||||
}
|
lockBodyScroll()
|
||||||
}
|
// 如果模态框包含输入框,则自动获取焦点
|
||||||
.pos {
|
if (dynamicShowInput.value || props.showInput) {
|
||||||
position: absolute;
|
// 使用 nextTick 确保在 DOM 更新后聚焦
|
||||||
left: 0;
|
nextTick(() => {
|
||||||
right: 0;
|
// 添加更长的延迟确保动画完成
|
||||||
top: 0;
|
setTimeout(() => {
|
||||||
}
|
// 确保 inputRef 存在并且可见
|
||||||
}
|
if (inputRef.value && inputRef.value.offsetParent !== null) {
|
||||||
}
|
inputRef.value.focus()
|
||||||
}
|
inputRef.value.select() // 选中所有文本,提升用户体验
|
||||||
</style>
|
}
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unlockBodyScroll()
|
||||||
|
// 重置输入框引用,确保下次打开时能正确获取焦点
|
||||||
|
if (inputRef.value) {
|
||||||
|
inputRef.value.blur()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 暴露 show 方法给父组件使用
|
||||||
|
defineExpose({ show })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pd-mask {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 999999999;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: inherit;
|
||||||
|
background: var(--confirmBg, inherit);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-confirm {
|
||||||
|
background: inherit;
|
||||||
|
background: var(--confirmTheme, #fff);
|
||||||
|
text-align: center;
|
||||||
|
color: var(--confirmColor, #636363);
|
||||||
|
font-size: var(--confirmFontSize, 1rem);
|
||||||
|
max-width: 75vw;
|
||||||
|
min-width: 20em;
|
||||||
|
box-shadow: 0px 35px 35px -10px rgba(0, 0, 0, 0.33);
|
||||||
|
border-radius: 10px;
|
||||||
|
position: relative;
|
||||||
|
white-space: break-spaces;
|
||||||
|
word-break: break-all;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-title {
|
||||||
|
position: relative;
|
||||||
|
font-size: 1.3em;
|
||||||
|
min-height: 1em;
|
||||||
|
background: var(--confirmBtnBg, #fafafa);
|
||||||
|
color: var(--confirmBtnColor, #636363);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-message {
|
||||||
|
position: relative;
|
||||||
|
border-top: 1px solid var(--confirmBtnBorder, #f1f1f1);
|
||||||
|
width: 100%;
|
||||||
|
max-height: 70vh;
|
||||||
|
border-bottom: 1px solid var(--confirmBtnBorder, #f1f1f1);
|
||||||
|
margin: 0 auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 2em 10%;
|
||||||
|
line-height: 1.4;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-input-container {
|
||||||
|
margin-block: 1.5em;
|
||||||
|
padding: 0 10% 1em;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.8em;
|
||||||
|
font-size: 1em;
|
||||||
|
border: 1px solid var(--confirmBtnBorder, #f1f1f1);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
outline: none;
|
||||||
|
background: var(--confirmTheme, #fff);
|
||||||
|
color: var(--confirmColor, #636363);
|
||||||
|
box-shadow: 0px 0px 4px inset rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-input::placeholder {
|
||||||
|
color: var(--black-20);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-input:focus {
|
||||||
|
border-color: #4d90fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-input::selection {
|
||||||
|
background-color: #4d90fe; /* 选中时的背景颜色 */
|
||||||
|
color: #ffffff; /* 选中时的文字颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-buttons {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-button {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1em;
|
||||||
|
appearance: none;
|
||||||
|
background: var(--confirmBtnBg, #fafafa);
|
||||||
|
color: var(--confirmBtnColor, #636363);
|
||||||
|
border: none;
|
||||||
|
border-right: 1px solid var(--confirmBtnBorder, #f1f1f1);
|
||||||
|
padding: 1em 0;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 当同时显示确认和取消按钮时,每个按钮占50%宽度 */
|
||||||
|
.pd-button:first-child:nth-last-child(2),
|
||||||
|
.pd-button:first-child:nth-last-child(2) ~ .pd-button {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 当只显示一个按钮时,该按钮占100%宽度 */
|
||||||
|
.pd-button:first-child:nth-last-child(1) {
|
||||||
|
width: 100%;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 只有最后一个按钮没有右边框 */
|
||||||
|
.pd-button:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,430 +1,473 @@
|
|||||||
<template>
|
<template>
|
||||||
<ion-page>
|
<ion-page>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<ion-content class="content">
|
<ion-content class="content">
|
||||||
<Header
|
<Header
|
||||||
:title="headerTitle"
|
:title="headerTitle"
|
||||||
:onAction="handleHeaderAction"
|
:onAction="handleHeaderAction"
|
||||||
actionIcon="create"
|
actionIcon="create"
|
||||||
leftType="settings"
|
leftType="settings"
|
||||||
:onLeftAction="handleSettingsPress"
|
:onLeftAction="handleSettingsPress"
|
||||||
:onFolderToggle="handleFolderToggle"
|
:onFolderToggle="handleFolderToggle"
|
||||||
:isFolderExpanded="isFolderExpanded"
|
:isFolderExpanded="isFolderExpanded"
|
||||||
:onTitlePress="handleFolderToggle"
|
:onTitlePress="handleFolderToggle"
|
||||||
slot="fixed" />
|
slot="fixed" />
|
||||||
|
|
||||||
<!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
|
<!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
|
||||||
<div v-if="isFolderExpanded" class="folder-list" slot="fixed">
|
<transition name="folder-slide">
|
||||||
<FolderManage
|
<div v-if="isFolderExpanded" class="folder-list" slot="fixed">
|
||||||
:allCount="allNotesCount"
|
<FolderManage
|
||||||
:starredCount="starredNotesCount"
|
:allCount="allNotesCount"
|
||||||
:trashCount="trashNotesCount"
|
:starredCount="starredNotesCount"
|
||||||
:archiveCount="0"
|
:trashCount="trashNotesCount"
|
||||||
:selectedFolder="currentFolder"
|
:archiveCount="0"
|
||||||
:onAllClick="handleAllNotesClick"
|
:selectedFolder="currentFolder"
|
||||||
:onStarredClick="handleStarredNotesClick"
|
:onAllClick="handleAllNotesClick"
|
||||||
:onTrashClick="handleTrashNotesClick" />
|
:onStarredClick="handleStarredNotesClick"
|
||||||
</div>
|
:onTrashClick="handleTrashNotesClick"
|
||||||
<!-- 点击外部区域收起文件夹列表的覆盖层 -->
|
:onFolderClick="handleFolderClick"
|
||||||
<div v-if="isFolderExpanded" @click="() => setIsFolderExpanded(false)" class="folder-overlay" slot="fixed"></div>
|
:onAddFolder="handleAddFolder" />
|
||||||
<div class="search-container">
|
</div>
|
||||||
<SearchBar v-model="searchQuery" @search="handleSearch" @clear="handleClearSearch" @focus="handleSearchFocus" @blur="handleSearchBlur" />
|
</transition>
|
||||||
</div>
|
<!-- 点击外部区域收起文件夹列表的覆盖层 -->
|
||||||
|
<div v-if="isFolderExpanded" @click="() => setIsFolderExpanded(false)" class="folder-overlay" slot="fixed"></div>
|
||||||
<div class="notes-container">
|
<div class="search-container">
|
||||||
<transition-group name="note-list" tag="div" class="notes-list">
|
<SearchBar v-model="searchQuery" @search="handleSearch" @clear="handleClearSearch" @focus="handleSearchFocus" @blur="handleSearchBlur" />
|
||||||
<div v-for="note in filteredAndSortedNotes" :key="note.id" class="note-item">
|
</div>
|
||||||
<NoteItem
|
|
||||||
:ref="
|
<div class="notes-container">
|
||||||
el => {
|
<transition-group name="note-list" tag="div" class="notes-list">
|
||||||
if (el) noteItemRefs[note.id] = el
|
<div v-for="note in filteredAndSortedNotes" :key="note.id" class="note-item">
|
||||||
}
|
<NoteItem
|
||||||
"
|
:ref="
|
||||||
:content="note.content"
|
el => {
|
||||||
:date="formatDate(note.updatedAt)"
|
if (el) noteItemRefs[note.id] = el
|
||||||
:isStarred="note.isStarred"
|
}
|
||||||
:isTop="note.isTop || false"
|
"
|
||||||
:hasImage="note.hasImage || false"
|
:content="note.content"
|
||||||
:onPress="() => handleNotePress(note.id)"
|
:date="formatDate(note.updatedAt)"
|
||||||
:onStarToggle="() => handleStarToggle(note.id)"
|
:isStarred="note.isStarred"
|
||||||
:onTopToggle="() => handleTopToggle(note.id)"
|
:isTop="note.isTop || false"
|
||||||
:onDelete="() => confirmDeleteNote(note.id)" />
|
:hasImage="note.hasImage || false"
|
||||||
</div>
|
:onPress="() => handleNotePress(note.id)"
|
||||||
</transition-group>
|
:onStarToggle="() => handleStarToggle(note.id)"
|
||||||
</div>
|
:onTopToggle="() => handleTopToggle(note.id)"
|
||||||
</ion-content>
|
:onDelete="() => confirmDeleteNote(note.id)" />
|
||||||
</div>
|
</div>
|
||||||
</ion-page>
|
</transition-group>
|
||||||
</template>
|
</div>
|
||||||
|
</ion-content>
|
||||||
<script setup>
|
</div>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
</ion-page>
|
||||||
import { useRouter } from 'vue-router'
|
</template>
|
||||||
import { useAppStore } from '../stores/useAppStore'
|
|
||||||
import NoteItem from '../components/NoteItem.vue'
|
<script setup>
|
||||||
import Header from '../components/Header.vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import FolderManage from '../components/FolderManage.vue'
|
import { useRouter } from 'vue-router'
|
||||||
import SearchBar from '../components/Search.vue'
|
import { useAppStore } from '../stores/useAppStore'
|
||||||
import { formatNoteListDate } from '../utils/dateUtils'
|
import NoteItem from '../components/NoteItem.vue'
|
||||||
import { IonContent, IonPage } from '@ionic/vue'
|
import Header from '../components/Header.vue'
|
||||||
|
import FolderManage from '../components/FolderManage.vue'
|
||||||
const store = useAppStore()
|
import SearchBar from '../components/Search.vue'
|
||||||
const router = useRouter()
|
import { formatNoteListDate } from '../utils/dateUtils'
|
||||||
const noteItemRefs = ref({})
|
import { IonContent, IonPage } from '@ionic/vue'
|
||||||
|
|
||||||
// 页面挂载时加载初始数据
|
const store = useAppStore()
|
||||||
onMounted(() => {
|
const router = useRouter()
|
||||||
// 检查URL参数是否包含mock数据加载指令,用于开发和演示
|
const noteItemRefs = ref({})
|
||||||
const urlParams = new URLSearchParams(window.location.search)
|
|
||||||
if (urlParams.get('mock') === 'true') {
|
// 页面挂载时加载初始数据
|
||||||
// 加载预设的模拟数据
|
onMounted(() => {
|
||||||
store.loadMockData()
|
// 检查URL参数是否包含mock数据加载指令,用于开发和演示
|
||||||
} else {
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
// 从Storage加载用户数据
|
if (urlParams.get('mock') === 'true') {
|
||||||
store.loadData()
|
// 加载预设的模拟数据
|
||||||
}
|
store.loadMockData()
|
||||||
})
|
} else {
|
||||||
|
// 从Storage加载用户数据
|
||||||
const searchQuery = ref('')
|
store.loadData()
|
||||||
const sortBy = ref('date') // 排序方式:'date'(按日期)、'title'(按标题)、'starred'(按星标)
|
}
|
||||||
const isFolderExpanded = ref(false) // 文件夹列表是否展开
|
})
|
||||||
const currentFolder = ref('all') // 当前选中的文件夹,默认是"全部便签"
|
|
||||||
const noteToDelete = ref(null)
|
const searchQuery = ref('')
|
||||||
|
const sortBy = ref('date') // 排序方式:'date'(按日期)、'title'(按标题)、'starred'(按星标)
|
||||||
// 计算加星便签数量(未删除的)
|
const isFolderExpanded = ref(false) // 文件夹列表是否展开
|
||||||
const starredNotesCount = computed(() => {
|
const currentFolder = ref('all') // 当前选中的文件夹,默认是"全部便签"
|
||||||
return store.notes.filter(note => note.isStarred && !note.isDeleted).length
|
const noteToDelete = ref(null)
|
||||||
})
|
|
||||||
|
// 计算加星便签数量(未删除的)
|
||||||
// 计算回收站便签数量
|
const starredNotesCount = computed(() => {
|
||||||
const trashNotesCount = computed(() => {
|
return store.notes.filter(note => note.isStarred && !note.isDeleted).length
|
||||||
return store.notes.filter(note => note.isDeleted).length
|
})
|
||||||
})
|
|
||||||
|
// 计算回收站便签数量
|
||||||
// 根据当前文件夹过滤便签
|
const trashNotesCount = computed(() => {
|
||||||
const filteredNotes = computed(() => {
|
return store.notes.filter(note => note.isDeleted).length
|
||||||
// 预处理搜索查询,提高性能
|
})
|
||||||
const lowerCaseQuery = searchQuery.value?.toLowerCase().trim() || ''
|
|
||||||
|
// 根据当前文件夹过滤便签
|
||||||
return store.notes.filter(note => {
|
const filteredNotes = computed(() => {
|
||||||
// 先检查搜索条件
|
// 预处理搜索查询,提高性能
|
||||||
const matchesSearch =
|
const lowerCaseQuery = searchQuery.value?.toLowerCase().trim() || ''
|
||||||
!lowerCaseQuery || (note.title && typeof note.title === 'string' && note.title.toLowerCase().includes(lowerCaseQuery)) || (note.content && typeof note.content === 'string' && note.content.toLowerCase().includes(lowerCaseQuery))
|
|
||||||
|
return store.notes.filter(note => {
|
||||||
if (!matchesSearch) return false
|
// 先检查搜索条件
|
||||||
|
const matchesSearch =
|
||||||
// 再检查文件夹条件
|
!lowerCaseQuery || (note.title && typeof note.title === 'string' && note.title.toLowerCase().includes(lowerCaseQuery)) || (note.content && typeof note.content === 'string' && note.content.toLowerCase().includes(lowerCaseQuery))
|
||||||
switch (currentFolder.value) {
|
|
||||||
case 'all':
|
if (!matchesSearch) return false
|
||||||
// 全部便签中不显示已删除的便签
|
|
||||||
return !note.isDeleted
|
// 再检查文件夹条件
|
||||||
case 'starred':
|
switch (currentFolder.value) {
|
||||||
// 加星便签中只显示未删除的加星便签
|
case 'all':
|
||||||
return note.isStarred && !note.isDeleted
|
// 全部便签中不显示已删除的便签
|
||||||
case 'trash':
|
return !note.isDeleted
|
||||||
// 回收站中只显示已删除的便签
|
case 'starred':
|
||||||
return note.isDeleted
|
// 加星便签中只显示未删除的加星便签
|
||||||
default:
|
return note.isStarred && !note.isDeleted
|
||||||
// 自定义文件夹中不显示已删除的便签
|
case 'trash':
|
||||||
return note.folderId === currentFolder.value && !note.isDeleted
|
// 回收站中只显示已删除的便签
|
||||||
}
|
return note.isDeleted
|
||||||
})
|
default:
|
||||||
})
|
// 自定义文件夹中不显示已删除的便签
|
||||||
|
return note.folderId === currentFolder.value && !note.isDeleted
|
||||||
// 过滤并排序便签列表
|
}
|
||||||
// 首先按置顶状态排序,置顶的便签排在前面
|
})
|
||||||
// 然后根据sortBy的值进行二次排序
|
})
|
||||||
const filteredAndSortedNotes = computed(() => {
|
|
||||||
return [...filteredNotes.value].sort((a, b) => {
|
// 过滤并排序便签列表
|
||||||
// 置顶的便签排在前面
|
// 首先按置顶状态排序,置顶的便签排在前面
|
||||||
if (a.isTop && !b.isTop) return -1
|
// 然后根据sortBy的值进行二次排序
|
||||||
if (!a.isTop && b.isTop) return 1
|
const filteredAndSortedNotes = computed(() => {
|
||||||
|
return [...filteredNotes.value].sort((a, b) => {
|
||||||
// 根据排序方式排序
|
// 置顶的便签排在前面
|
||||||
switch (sortBy.value) {
|
if (a.isTop && !b.isTop) return -1
|
||||||
case 'title':
|
if (!a.isTop && b.isTop) return 1
|
||||||
// 按标题字母顺序排序
|
|
||||||
return a.title.localeCompare(b.title)
|
// 根据排序方式排序
|
||||||
case 'starred':
|
switch (sortBy.value) {
|
||||||
// 按星标状态排序,加星的便签排在前面
|
case 'title':
|
||||||
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0)
|
// 按标题字母顺序排序
|
||||||
case 'date':
|
return a.title.localeCompare(b.title)
|
||||||
default:
|
case 'starred':
|
||||||
// 按更新时间倒序排列(最新的在前)
|
// 按星标状态排序,加星的便签排在前面
|
||||||
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0)
|
||||||
}
|
case 'date':
|
||||||
})
|
default:
|
||||||
})
|
// 按更新时间倒序排列(最新的在前)
|
||||||
|
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||||
// 计算头部标题
|
}
|
||||||
const headerTitle = computed(() => {
|
})
|
||||||
switch (currentFolder.value) {
|
})
|
||||||
case 'all':
|
|
||||||
return '全部便签'
|
// 计算头部标题
|
||||||
case 'starred':
|
const headerTitle = computed(() => {
|
||||||
return '加星便签'
|
switch (currentFolder.value) {
|
||||||
case 'trash':
|
case 'all':
|
||||||
return '回收站'
|
return '全部便签'
|
||||||
default:
|
case 'starred':
|
||||||
return '全部便签'
|
return '加星便签'
|
||||||
}
|
case 'trash':
|
||||||
})
|
return '回收站'
|
||||||
|
default:
|
||||||
// 计算全部便签数量(未删除的)
|
// 查找自定义文件夹的名称
|
||||||
const allNotesCount = computed(() => {
|
const folder = store.folders.find(f => f.id === currentFolder.value)
|
||||||
return store.notes.filter(note => !note.isDeleted).length
|
return folder ? folder.name : '全部便签'
|
||||||
})
|
}
|
||||||
|
})
|
||||||
const handleNotePress = noteId => {
|
|
||||||
// 检查是否有便签条处于展开状态
|
// 计算全部便签数量(未删除的)
|
||||||
let hasSlidedNote = false
|
const allNotesCount = computed(() => {
|
||||||
Object.values(noteItemRefs.value).forEach(noteItem => {
|
return store.notes.filter(note => !note.isDeleted).length
|
||||||
// 注意:isSlided是ref值,需要通过.value访问
|
})
|
||||||
if (noteItem && noteItem.getSlideState()) {
|
|
||||||
hasSlidedNote = true
|
const handleNotePress = noteId => {
|
||||||
noteItem.resetSlideState()
|
// 检查是否有便签条处于展开状态
|
||||||
}
|
let hasSlidedNote = false
|
||||||
})
|
Object.values(noteItemRefs.value).forEach(noteItem => {
|
||||||
|
// 注意:isSlided是ref值,需要通过.value访问
|
||||||
// 如果有便签条处于展开状态,不跳转到编辑页面
|
if (noteItem && noteItem.getSlideState()) {
|
||||||
if (hasSlidedNote) {
|
hasSlidedNote = true
|
||||||
return
|
noteItem.resetSlideState()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
// 使用vue-router导航到编辑页面
|
|
||||||
router.push(`/editor/${noteId}`)
|
// 如果有便签条处于展开状态,不跳转到编辑页面
|
||||||
}
|
if (hasSlidedNote) {
|
||||||
|
return
|
||||||
const handleAddNote = () => {
|
}
|
||||||
// 使用vue-router导航到新建便签页面
|
|
||||||
router.push('/editor')
|
// 使用vue-router导航到编辑页面
|
||||||
}
|
router.push(`/editor/${noteId}`)
|
||||||
|
}
|
||||||
// 处理Header组件的操作按钮点击事件
|
|
||||||
const handleHeaderAction = actionType => {
|
const handleAddNote = () => {
|
||||||
if (actionType === 'create') {
|
// 使用vue-router导航到新建便签页面
|
||||||
handleAddNote()
|
router.push('/editor')
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// 处理Header组件的操作按钮点击事件
|
||||||
const handleStarToggle = async noteId => {
|
const handleHeaderAction = actionType => {
|
||||||
const note = store.notes.find(n => n.id === noteId)
|
if (actionType === 'create') {
|
||||||
if (note) {
|
handleAddNote()
|
||||||
try {
|
}
|
||||||
await store.updateNote(noteId, { isStarred: !note.isStarred })
|
}
|
||||||
console.log(`便签 ${noteId} 星标状态已更新`)
|
|
||||||
} catch (error) {
|
const handleStarToggle = async noteId => {
|
||||||
console.error('更新便签星标状态失败:', error)
|
const note = store.notes.find(n => n.id === noteId)
|
||||||
}
|
if (note) {
|
||||||
}
|
try {
|
||||||
}
|
await store.updateNote(noteId, { isStarred: !note.isStarred })
|
||||||
|
console.log(`便签 ${noteId} 星标状态已更新`)
|
||||||
const handleTopToggle = async noteId => {
|
} catch (error) {
|
||||||
const note = store.notes.find(n => n.id === noteId)
|
console.error('更新便签星标状态失败:', error)
|
||||||
if (note) {
|
}
|
||||||
try {
|
}
|
||||||
await store.updateNote(noteId, { isTop: !note.isTop })
|
}
|
||||||
console.log(`便签 ${noteId} 置顶状态已更新`)
|
|
||||||
} catch (error) {
|
const handleTopToggle = async noteId => {
|
||||||
console.error('更新便签置顶状态失败:', error)
|
const note = store.notes.find(n => n.id === noteId)
|
||||||
}
|
if (note) {
|
||||||
}
|
try {
|
||||||
}
|
await store.updateNote(noteId, { isTop: !note.isTop })
|
||||||
|
console.log(`便签 ${noteId} 置顶状态已更新`)
|
||||||
const confirmDeleteNote = async noteId => {
|
} catch (error) {
|
||||||
noteToDelete.value = noteId
|
console.error('更新便签置顶状态失败:', error)
|
||||||
if (noteToDelete.value) {
|
}
|
||||||
try {
|
}
|
||||||
// 检查当前是否在回收站中
|
}
|
||||||
if (currentFolder.value === 'trash') {
|
|
||||||
// 在回收站中删除便签,彻底删除
|
const confirmDeleteNote = async noteId => {
|
||||||
await store.permanentlyDeleteNote(noteToDelete.value)
|
noteToDelete.value = noteId
|
||||||
console.log(`便签 ${noteToDelete.value} 已彻底删除`)
|
if (noteToDelete.value) {
|
||||||
} else {
|
try {
|
||||||
// 不在回收站中,将便签移至回收站
|
// 检查当前是否在回收站中
|
||||||
await store.moveToTrash(noteToDelete.value)
|
if (currentFolder.value === 'trash') {
|
||||||
console.log(`便签 ${noteToDelete.value} 已移至回收站`)
|
// 在回收站中删除便签,彻底删除
|
||||||
}
|
await store.permanentlyDeleteNote(noteToDelete.value)
|
||||||
noteToDelete.value = null
|
console.log(`便签 ${noteToDelete.value} 已彻底删除`)
|
||||||
} catch (error) {
|
} else {
|
||||||
console.error('删除便签失败:', error)
|
// 不在回收站中,将便签移至回收站
|
||||||
}
|
await store.moveToTrash(noteToDelete.value)
|
||||||
}
|
console.log(`便签 ${noteToDelete.value} 已移至回收站`)
|
||||||
}
|
}
|
||||||
|
noteToDelete.value = null
|
||||||
// 处理排序方式切换
|
} catch (error) {
|
||||||
// 循环切换排序选项:按日期 -> 按标题 -> 按星标 -> 按日期...
|
console.error('删除便签失败:', error)
|
||||||
const handleSort = () => {
|
}
|
||||||
const sortOptions = ['date', 'title', 'starred']
|
}
|
||||||
const currentIndex = sortOptions.indexOf(sortBy.value)
|
}
|
||||||
const nextIndex = (currentIndex + 1) % sortOptions.length
|
|
||||||
sortBy.value = sortOptions[nextIndex]
|
// 处理排序方式切换
|
||||||
console.log('当前排序方式:', sortOptions[nextIndex])
|
// 循环切换排序选项:按日期 -> 按标题 -> 按星标 -> 按日期...
|
||||||
}
|
const handleSort = () => {
|
||||||
|
const sortOptions = ['date', 'title', 'starred']
|
||||||
const handleAllNotesClick = () => {
|
const currentIndex = sortOptions.indexOf(sortBy.value)
|
||||||
setCurrentFolder('all')
|
const nextIndex = (currentIndex + 1) % sortOptions.length
|
||||||
setIsFolderExpanded(false)
|
sortBy.value = sortOptions[nextIndex]
|
||||||
}
|
console.log('当前排序方式:', sortOptions[nextIndex])
|
||||||
|
}
|
||||||
const handleStarredNotesClick = () => {
|
|
||||||
setCurrentFolder('starred')
|
const handleAllNotesClick = () => {
|
||||||
setIsFolderExpanded(false)
|
setCurrentFolder('all')
|
||||||
}
|
setIsFolderExpanded(false)
|
||||||
|
}
|
||||||
const handleTrashNotesClick = () => {
|
|
||||||
setCurrentFolder('trash')
|
const handleStarredNotesClick = () => {
|
||||||
setIsFolderExpanded(false)
|
setCurrentFolder('starred')
|
||||||
}
|
setIsFolderExpanded(false)
|
||||||
|
}
|
||||||
const handleFolderPress = () => {
|
|
||||||
// 使用vue-router导航到文件夹页面
|
const handleTrashNotesClick = () => {
|
||||||
router.push('/folders')
|
setCurrentFolder('trash')
|
||||||
}
|
setIsFolderExpanded(false)
|
||||||
|
}
|
||||||
const handleSettingsPress = () => {
|
|
||||||
// 使用vue-router导航到设置页面
|
const handleFolderClick = folderId => {
|
||||||
router.push('/settings')
|
setCurrentFolder(folderId)
|
||||||
}
|
setIsFolderExpanded(false)
|
||||||
|
}
|
||||||
const handleFolderToggle = () => {
|
|
||||||
// 在实际应用中,这里会触发文件夹列表的展开/收起
|
const handleAddFolder = () => {
|
||||||
isFolderExpanded.value = !isFolderExpanded.value
|
// 文件夹添加功能已在FolderManage组件中实现
|
||||||
}
|
// 这里只需关闭文件夹列表
|
||||||
|
setIsFolderExpanded(false)
|
||||||
const handleSearch = query => {
|
}
|
||||||
// 搜索功能已在computed属性filteredAndSortedNotes中实现
|
|
||||||
console.log('搜索:', query)
|
const handleFolderPress = () => {
|
||||||
|
// 使用vue-router导航到文件夹页面
|
||||||
// 可以在这里添加搜索统计或其它功能
|
router.push('/folders')
|
||||||
if (query && query.length > 0) {
|
}
|
||||||
console.log(`找到 ${filteredAndSortedNotes.value.length} 个匹配的便签`)
|
|
||||||
}
|
const handleSettingsPress = () => {
|
||||||
}
|
// 使用vue-router导航到设置页面
|
||||||
|
router.push('/settings')
|
||||||
const handleClearSearch = () => {
|
}
|
||||||
// 清除搜索已在v-model中处理
|
|
||||||
console.log('搜索已清除')
|
const handleFolderToggle = () => {
|
||||||
|
// 在实际应用中,这里会触发文件夹列表的展开/收起
|
||||||
// 清除搜索后可以重置一些状态
|
isFolderExpanded.value = !isFolderExpanded.value
|
||||||
setSearchQuery('')
|
}
|
||||||
}
|
|
||||||
|
const handleSearch = query => {
|
||||||
const handleSearchFocus = () => {
|
// 搜索功能已在computed属性filteredAndSortedNotes中实现
|
||||||
console.log('搜索栏获得焦点')
|
console.log('搜索:', query)
|
||||||
// 可以在这里添加获得焦点时的特殊处理
|
|
||||||
}
|
// 可以在这里添加搜索统计或其它功能
|
||||||
|
if (query && query.length > 0) {
|
||||||
const handleSearchBlur = () => {
|
console.log(`找到 ${filteredAndSortedNotes.value.length} 个匹配的便签`)
|
||||||
console.log('搜索栏失去焦点')
|
}
|
||||||
// 可以在这里添加失去焦点时的特殊处理
|
}
|
||||||
}
|
|
||||||
|
const handleClearSearch = () => {
|
||||||
// 防抖函数,用于避免频繁触发搜索
|
// 清除搜索已在v-model中处理
|
||||||
// 通过延迟执行函数,只在最后一次调用后执行
|
console.log('搜索已清除')
|
||||||
const debounceSearch = (func, delay) => {
|
|
||||||
let timeoutId
|
// 清除搜索后可以重置一些状态
|
||||||
return function (...args) {
|
setSearchQuery('')
|
||||||
clearTimeout(timeoutId)
|
}
|
||||||
timeoutId = setTimeout(() => func.apply(this, args), delay)
|
|
||||||
}
|
const handleSearchFocus = () => {
|
||||||
}
|
console.log('搜索栏获得焦点')
|
||||||
|
// 可以在这里添加获得焦点时的特殊处理
|
||||||
// 防抖搜索处理函数,延迟300ms执行搜索
|
}
|
||||||
const debouncedHandleSearch = debounceSearch(query => {
|
|
||||||
handleSearch(query)
|
const handleSearchBlur = () => {
|
||||||
}, 300)
|
console.log('搜索栏失去焦点')
|
||||||
|
// 可以在这里添加失去焦点时的特殊处理
|
||||||
// 改进的日期格式化函数
|
}
|
||||||
const formatDate = dateString => {
|
|
||||||
return formatNoteListDate(dateString)
|
// 防抖函数,用于避免频繁触发搜索
|
||||||
}
|
// 通过延迟执行函数,只在最后一次调用后执行
|
||||||
|
const debounceSearch = (func, delay) => {
|
||||||
const setCurrentFolder = folder => {
|
let timeoutId
|
||||||
currentFolder.value = folder
|
return function (...args) {
|
||||||
}
|
clearTimeout(timeoutId)
|
||||||
|
timeoutId = setTimeout(() => func.apply(this, args), delay)
|
||||||
const setIsFolderExpanded = expanded => {
|
}
|
||||||
isFolderExpanded.value = expanded
|
}
|
||||||
}
|
|
||||||
|
// 防抖搜索处理函数,延迟300ms执行搜索
|
||||||
const setSearchQuery = query => {
|
const debouncedHandleSearch = debounceSearch(query => {
|
||||||
searchQuery.value = query
|
handleSearch(query)
|
||||||
}
|
}, 300)
|
||||||
|
|
||||||
const notes = computed(() => store.notes)
|
// 改进的日期格式化函数
|
||||||
</script>
|
const formatDate = dateString => {
|
||||||
<style lang="less" scoped>
|
return formatNoteListDate(dateString)
|
||||||
.container {
|
}
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
const setCurrentFolder = folder => {
|
||||||
background: url(/assets/icons/drawable-xxhdpi/note_background.png);
|
currentFolder.value = folder
|
||||||
background-size: cover;
|
}
|
||||||
}
|
|
||||||
|
const setIsFolderExpanded = expanded => {
|
||||||
.folder-list {
|
isFolderExpanded.value = expanded
|
||||||
position: absolute;
|
}
|
||||||
top: 3.125rem;
|
|
||||||
left: 10%;
|
const setSearchQuery = query => {
|
||||||
right: 10%;
|
searchQuery.value = query
|
||||||
z-index: 1000;
|
}
|
||||||
background-color: var(--background-card);
|
|
||||||
border-radius: 0.5rem;
|
const notes = computed(() => store.notes)
|
||||||
box-shadow: 0 0.125rem 0.25rem var(--shadow);
|
</script>
|
||||||
border: 1px solid #f0ece7;
|
<style lang="less" scoped>
|
||||||
overflow: hidden;
|
.container {
|
||||||
}
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
.folder-overlay {
|
background: url(/assets/icons/drawable-xxhdpi/note_background.png);
|
||||||
position: absolute;
|
background-size: cover;
|
||||||
top: 0;
|
}
|
||||||
left: 0;
|
|
||||||
right: 0;
|
.folder-list {
|
||||||
bottom: 0;
|
position: absolute;
|
||||||
background-color: transparent;
|
top: 3.125rem;
|
||||||
z-index: 99;
|
left: 10%;
|
||||||
}
|
right: 10%;
|
||||||
.content {
|
z-index: 1000;
|
||||||
--background: transparent;
|
background-color: var(--background-card);
|
||||||
--padding-top: 4.5rem;
|
border-radius: 0.5rem;
|
||||||
--padding-bottom: 2rem;
|
box-shadow: 0 0.125rem 0.25rem var(--shadow);
|
||||||
}
|
border: 1px solid #f0ece7;
|
||||||
|
overflow: hidden;
|
||||||
.search-container {
|
}
|
||||||
padding: 0.8rem 0.5rem;
|
|
||||||
}
|
.folder-overlay {
|
||||||
|
position: absolute;
|
||||||
.notes-container {
|
top: 0;
|
||||||
flex: 1;
|
left: 0;
|
||||||
position: relative;
|
right: 0;
|
||||||
}
|
bottom: 0;
|
||||||
|
background-color: transparent;
|
||||||
.notes-list {
|
z-index: 99;
|
||||||
position: relative;
|
}
|
||||||
}
|
.content {
|
||||||
|
--background: transparent;
|
||||||
.note-item {
|
--padding-top: 4.5rem;
|
||||||
margin: 0.6rem 0;
|
--padding-bottom: 2rem;
|
||||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
}
|
||||||
}
|
|
||||||
|
.search-container {
|
||||||
/* 便签列表动画 */
|
padding: 0.8rem 0.5rem;
|
||||||
.note-list-enter-active,
|
}
|
||||||
.note-list-leave-active {
|
|
||||||
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
.notes-container {
|
||||||
}
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
.note-list-leave-to {
|
}
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(-30px);
|
.notes-list {
|
||||||
}
|
position: relative;
|
||||||
|
}
|
||||||
.note-list-move {
|
|
||||||
transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
.note-item {
|
||||||
}
|
margin: 0.6rem 0;
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
.note-list-leave-active {
|
}
|
||||||
position: absolute;
|
|
||||||
width: calc(100% - 1rem);
|
/* 便签列表动画 */
|
||||||
}
|
.note-list-enter-active,
|
||||||
</style>
|
.note-list-leave-active {
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-list-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-list-move {
|
||||||
|
transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-list-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文件夹列表动画 */
|
||||||
|
.folder-slide-enter-active,
|
||||||
|
.folder-slide-leave-active {
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-slide-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8) translateY(-20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-slide-enter-to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-slide-leave-from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-slide-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8) translateY(-20px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,336 +1,383 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import * as storage from '../utils/indexedDBStorage'
|
import * as storage from '../utils/indexedDBStorage'
|
||||||
import { getCurrentDateTime, getPastDate } from '../utils/dateUtils'
|
import { getCurrentDateTime, getPastDate } from '../utils/dateUtils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用状态管理Store
|
* 应用状态管理Store
|
||||||
* 使用Pinia进行状态管理,包含便签、文件夹和设置数据
|
* 使用Pinia进行状态管理,包含便签、文件夹和设置数据
|
||||||
*/
|
*/
|
||||||
export const useAppStore = defineStore('app', {
|
export const useAppStore = defineStore('app', {
|
||||||
/**
|
/**
|
||||||
* 状态定义
|
* 状态定义
|
||||||
* 包含应用的核心数据:便签列表、文件夹列表和设置
|
* 包含应用的核心数据:便签列表、文件夹列表和设置
|
||||||
*/
|
*/
|
||||||
state: () => ({
|
state: () => ({
|
||||||
notes: [], // 便签列表
|
notes: [], // 便签列表
|
||||||
folders: [], // 文件夹列表
|
folders: [], // 文件夹列表
|
||||||
settings: { cloudSync: false, darkMode: false }, // 应用设置
|
settings: { cloudSync: false, darkMode: false }, // 应用设置
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算属性
|
* 计算属性
|
||||||
* 基于状态派生的计算值
|
* 基于状态派生的计算值
|
||||||
*/
|
*/
|
||||||
getters: {
|
getters: {
|
||||||
/**
|
/**
|
||||||
* 计算加星便签数量
|
* 计算加星便签数量
|
||||||
* @param {Object} state - 当前状态对象
|
* @param {Object} state - 当前状态对象
|
||||||
* @returns {number} 加星便签的数量
|
* @returns {number} 加星便签的数量
|
||||||
*/
|
*/
|
||||||
starredNotesCount: state => {
|
starredNotesCount: state => {
|
||||||
return state.notes.filter(note => note.isStarred).length
|
return state.notes.filter(note => note.isStarred).length
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算所有便签数量
|
* 计算所有便签数量
|
||||||
* @param {Object} state - 当前状态对象
|
* @param {Object} state - 当前状态对象
|
||||||
* @returns {number} 所有便签的数量
|
* @returns {number} 所有便签的数量
|
||||||
*/
|
*/
|
||||||
allNotesCount: state => {
|
allNotesCount: state => {
|
||||||
return state.notes.length
|
return state.notes.length
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态变更操作
|
* 状态变更操作
|
||||||
* 包含所有修改状态的方法
|
* 包含所有修改状态的方法
|
||||||
*/
|
*/
|
||||||
actions: {
|
actions: {
|
||||||
/**
|
/**
|
||||||
* 初始化数据
|
* 初始化数据
|
||||||
* 从Storage加载便签、文件夹和设置数据
|
* 从Storage加载便签、文件夹和设置数据
|
||||||
* 如果没有数据则加载预设的mock数据
|
* 如果没有数据则加载预设的mock数据
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async loadData() {
|
async loadData() {
|
||||||
try {
|
try {
|
||||||
// 从Storage加载数据
|
// 从Storage加载数据
|
||||||
const loadedNotes = await storage.getNotes()
|
const loadedNotes = await storage.getNotes()
|
||||||
const loadedFolders = await storage.getFolders()
|
const loadedFolders = await storage.getFolders()
|
||||||
const loadedSettings = await storage.getSettings()
|
const loadedSettings = await storage.getSettings()
|
||||||
|
|
||||||
// 如果没有数据,则加载mock数据
|
// 如果没有数据,则加载mock数据
|
||||||
if (loadedNotes.length === 0 && loadedFolders.length === 0) {
|
if (loadedNotes.length === 0 && loadedFolders.length === 0) {
|
||||||
this.loadMockData()
|
this.loadMockData()
|
||||||
} else {
|
} else {
|
||||||
// 否则使用加载的数据
|
// 否则使用加载的数据
|
||||||
this.notes = loadedNotes
|
this.notes = loadedNotes
|
||||||
this.folders = loadedFolders
|
this.folders = loadedFolders
|
||||||
this.settings = loadedSettings
|
this.settings = loadedSettings
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error)
|
console.error('Error loading data:', error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载预设的mock数据
|
* 加载预设的mock数据
|
||||||
* 用于开发和演示目的,提供示例便签、文件夹和设置
|
* 用于开发和演示目的,提供示例便签、文件夹和设置
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async loadMockData() {
|
async loadMockData() {
|
||||||
// Mock notes - 使用固定的日期值以避免每次运行时变化
|
// Mock notes - 使用固定的日期值以避免每次运行时变化
|
||||||
const fixedCurrentDate = '2025-10-12T10:00:00.000Z';
|
const fixedCurrentDate = '2025-10-12T10:00:00.000Z';
|
||||||
|
|
||||||
// 预设的便签示例数据 - 仅保留一条关于应用功能介绍和示例的便签
|
// 预设的便签示例数据 - 仅保留一条关于应用功能介绍和示例的便签
|
||||||
const mockNotes = [
|
const mockNotes = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
title: '欢迎使用锤子便签',
|
title: '欢迎使用锤子便签',
|
||||||
content: '<p>这是一个功能强大的便签应用,您可以在这里记录您的想法、待办事项等。</p><br><h2>功能介绍</h2><p>1. 创建和编辑便签<br>2. 为便签加星和置顶<br>3. 将便签分类到文件夹<br>4. 搜索便签内容<br>5. 回收站功能</p><br><h2>编辑器功能演示</h2><br><h2>标题格式</h2><p>点击标题按钮可创建居中的标题</p><br><h2>待办事项</h2><div class="todo-container"><div class="todo-icon"></div><div contenteditable="true" class="todo-content">这是一个待办事项</div></div><div class="todo-container"><div class="todo-icon completed"></div><div contenteditable="true" class="todo-content" style="color: var(--text-tertiary); text-decoration: line-through;">这是一个已完成的待办事项</div></div><br><h2>列表格式</h2><ul><li>无序列表项1</li><li>无序列表项2</li></ul><br><h2>文本格式</h2><p><strong>加粗文本</strong></p><br><h2>引用格式</h2><div class="quote-container"><div class="quote-icon"></div><div class="quote-content">这是一段引用文本<br>可以用来引用他人的话语</div></div><br><h2>图片</h2><p>点击图片按钮可以插入图片,长按图片可以拖拽排序</p>',
|
content: '<p>这是一个功能强大的便签应用,您可以在这里记录您的想法、待办事项等。</p><br><h2>功能介绍</h2><p>1. 创建和编辑便签<br>2. 为便签加星和置顶<br>3. 将便签分类到文件夹<br>4. 搜索便签内容<br>5. 回收站功能</p><br><h2>编辑器功能演示</h2><br><h2>标题格式</h2><p>点击标题按钮可创建居中的标题</p><br><h2>待办事项</h2><div class="todo-container"><div class="todo-icon"></div><div contenteditable="true" class="todo-content">这是一个待办事项</div></div><div class="todo-container"><div class="todo-icon completed"></div><div contenteditable="true" class="todo-content" style="color: var(--text-tertiary); text-decoration: line-through;">这是一个已完成的待办事项</div></div><br><h2>列表格式</h2><ul><li>无序列表项1</li><li>无序列表项2</li></ul><br><h2>文本格式</h2><p><strong>加粗文本</strong></p><br><h2>引用格式</h2><div class="quote-container"><div class="quote-icon"></div><div class="quote-content">这是一段引用文本<br>可以用来引用他人的话语</div></div><br><h2>图片</h2><p>点击图片按钮可以插入图片,长按图片可以拖拽排序</p>',
|
||||||
createdAt: fixedCurrentDate,
|
createdAt: fixedCurrentDate,
|
||||||
updatedAt: fixedCurrentDate,
|
updatedAt: fixedCurrentDate,
|
||||||
folderId: null,
|
folderId: null,
|
||||||
isStarred: true, // 加星便签
|
isStarred: true, // 加星便签
|
||||||
isTop: true, // 置顶便签
|
isTop: true, // 置顶便签
|
||||||
hasImage: true, // 包含图片
|
hasImage: true, // 包含图片
|
||||||
isDeleted: false, // 未删除
|
isDeleted: false, // 未删除
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// Mock folders - 使用固定的日期值
|
// Mock folders - 使用固定的日期值
|
||||||
// 预设的文件夹示例数据
|
// 预设的文件夹示例数据
|
||||||
const mockFolders = [
|
const mockFolders = [
|
||||||
{
|
{
|
||||||
id: 'folder1',
|
id: 'folder1',
|
||||||
name: '工作',
|
name: '工作',
|
||||||
createdAt: '2025-10-12T10:00:00.000Z',
|
createdAt: '2025-10-12T10:00:00.000Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'folder2',
|
id: 'folder2',
|
||||||
name: '个人',
|
name: '个人',
|
||||||
createdAt: '2025-10-12T10:00:00.000Z',
|
createdAt: '2025-10-12T10:00:00.000Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'folder3',
|
id: 'folder3',
|
||||||
name: '学习',
|
name: '学习',
|
||||||
createdAt: '2025-10-12T10:00:00.000Z',
|
createdAt: '2025-10-12T10:00:00.000Z',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// Mock settings
|
// Mock settings
|
||||||
// 预设的设置示例数据
|
// 预设的设置示例数据
|
||||||
const mockSettings = {
|
const mockSettings = {
|
||||||
cloudSync: false, // 云同步关闭
|
cloudSync: false, // 云同步关闭
|
||||||
darkMode: false, // 深色模式关闭
|
darkMode: false, // 深色模式关闭
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新store状态
|
// 更新store状态
|
||||||
this.notes = mockNotes
|
this.notes = mockNotes
|
||||||
this.folders = mockFolders
|
this.folders = mockFolders
|
||||||
this.settings = mockSettings
|
this.settings = mockSettings
|
||||||
|
|
||||||
// 保存到Storage
|
// 保存到Storage
|
||||||
await storage.saveNotes(mockNotes)
|
await storage.saveNotes(mockNotes)
|
||||||
await storage.saveFolders(mockFolders)
|
await storage.saveFolders(mockFolders)
|
||||||
await storage.saveSettings(mockSettings)
|
await storage.saveSettings(mockSettings)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存便签数据到Storage
|
* 保存便签数据到Storage
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async saveNotes() {
|
async saveNotes() {
|
||||||
try {
|
try {
|
||||||
await storage.saveNotes(this.notes)
|
await storage.saveNotes(this.notes)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving notes:', error)
|
console.error('Error saving notes:', error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存文件夹数据到Storage
|
* 保存文件夹数据到Storage
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async saveFolders() {
|
async saveFolders() {
|
||||||
try {
|
try {
|
||||||
await storage.saveFolders(this.folders)
|
await storage.saveFolders(this.folders)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving folders:', error)
|
console.error('Error saving folders:', error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存设置数据到Storage
|
* 保存设置数据到Storage
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async saveSettings() {
|
async saveSettings() {
|
||||||
try {
|
try {
|
||||||
await storage.saveSettings(this.settings)
|
await storage.saveSettings(this.settings)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving settings:', error)
|
console.error('Error saving settings:', error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 便签操作函数
|
* 便签操作函数
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加新便签
|
* 添加新便签
|
||||||
* @param {Object} note - 便签对象
|
* @param {Object} note - 便签对象
|
||||||
* @returns {Promise<Object>} 新创建的便签对象
|
* @returns {Promise<Object>} 新创建的便签对象
|
||||||
*/
|
*/
|
||||||
async addNote(note) {
|
async addNote(note) {
|
||||||
try {
|
try {
|
||||||
const newNote = await storage.addNote(note)
|
const newNote = await storage.addNote(note)
|
||||||
this.notes.push(newNote)
|
this.notes.push(newNote)
|
||||||
|
|
||||||
return newNote
|
return newNote
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding note:', error)
|
console.error('Error adding note:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新便签
|
* 更新便签
|
||||||
* @param {string} id - 便签ID
|
* @param {string} id - 便签ID
|
||||||
* @param {Object} updates - 要更新的属性对象
|
* @param {Object} updates - 要更新的属性对象
|
||||||
* @returns {Promise<Object>} 更新后的便签对象
|
* @returns {Promise<Object>} 更新后的便签对象
|
||||||
*/
|
*/
|
||||||
async updateNote(id, updates) {
|
async updateNote(id, updates) {
|
||||||
try {
|
try {
|
||||||
const updatedNote = await storage.updateNote(id, updates)
|
const updatedNote = await storage.updateNote(id, updates)
|
||||||
if (updatedNote) {
|
if (updatedNote) {
|
||||||
const index = this.notes.findIndex(note => note.id === id)
|
const index = this.notes.findIndex(note => note.id === id)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.notes[index] = updatedNote
|
this.notes[index] = updatedNote
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return updatedNote
|
return updatedNote
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating note:', error)
|
console.error('Error updating note:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除便签
|
* 删除便签
|
||||||
* @param {string} id - 要删除的便签ID
|
* @param {string} id - 要删除的便签ID
|
||||||
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
||||||
*/
|
*/
|
||||||
async deleteNote(id) {
|
async deleteNote(id) {
|
||||||
try {
|
try {
|
||||||
const result = await storage.deleteNote(id)
|
const result = await storage.deleteNote(id)
|
||||||
if (result) {
|
if (result) {
|
||||||
this.notes = this.notes.filter(note => note.id !== id)
|
this.notes = this.notes.filter(note => note.id !== id)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting note:', error)
|
console.error('Error deleting note:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将便签移至回收站
|
* 将便签移至回收站
|
||||||
* 将便签标记为已删除状态,并记录删除时间
|
* 将便签标记为已删除状态,并记录删除时间
|
||||||
* @param {string} id - 便签ID
|
* @param {string} id - 便签ID
|
||||||
* @returns {Promise<Object>} 更新后的便签对象
|
* @returns {Promise<Object>} 更新后的便签对象
|
||||||
*/
|
*/
|
||||||
async moveToTrash(id) {
|
async moveToTrash(id) {
|
||||||
try {
|
try {
|
||||||
const updatedNote = await storage.updateNote(id, { isDeleted: true, deletedAt: getCurrentDateTime() })
|
const updatedNote = await storage.updateNote(id, { isDeleted: true, deletedAt: getCurrentDateTime() })
|
||||||
if (updatedNote) {
|
if (updatedNote) {
|
||||||
const index = this.notes.findIndex(note => note.id === id)
|
const index = this.notes.findIndex(note => note.id === id)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.notes[index] = updatedNote
|
this.notes[index] = updatedNote
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return updatedNote
|
return updatedNote
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error moving note to trash:', error)
|
console.error('Error moving note to trash:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 永久删除便签
|
* 永久删除便签
|
||||||
* 从便签列表中彻底移除便签
|
* 从便签列表中彻底移除便签
|
||||||
* @param {string} id - 便签ID
|
* @param {string} id - 便签ID
|
||||||
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
||||||
*/
|
*/
|
||||||
async permanentlyDeleteNote(id) {
|
async permanentlyDeleteNote(id) {
|
||||||
try {
|
try {
|
||||||
const result = await storage.deleteNote(id)
|
const result = await storage.deleteNote(id)
|
||||||
if (result) {
|
if (result) {
|
||||||
this.notes = this.notes.filter(note => note.id !== id)
|
this.notes = this.notes.filter(note => note.id !== id)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error permanently deleting note:', error)
|
console.error('Error permanently deleting note:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件夹操作函数
|
* 文件夹操作函数
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加新文件夹
|
* 添加新文件夹
|
||||||
* @param {Object} folder - 文件夹对象
|
* @param {Object} folder - 文件夹对象
|
||||||
* @returns {Promise<Object>} 新创建的文件夹对象
|
* @returns {Promise<Object>} 新创建的文件夹对象
|
||||||
*/
|
*/
|
||||||
async addFolder(folder) {
|
async addFolder(folder) {
|
||||||
try {
|
try {
|
||||||
const newFolder = await storage.addFolder(folder)
|
const newFolder = await storage.addFolder(folder)
|
||||||
this.folders.push(newFolder)
|
this.folders.push(newFolder)
|
||||||
return newFolder
|
return newFolder
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding folder:', error)
|
console.error('Error adding folder:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置操作函数
|
* 更新文件夹
|
||||||
*/
|
* @param {string} id - 文件夹ID
|
||||||
|
* @param {Object} updates - 要更新的属性对象
|
||||||
/**
|
* @returns {Promise<Object>} 更新后的文件夹对象
|
||||||
* 更新设置
|
*/
|
||||||
* @param {Object} newSettings - 新的设置对象
|
async updateFolder(id, updates) {
|
||||||
* @returns {Promise<void>}
|
try {
|
||||||
*/
|
const updatedFolder = await storage.updateFolder(id, updates)
|
||||||
async updateSettings(newSettings) {
|
if (updatedFolder) {
|
||||||
try {
|
const index = this.folders.findIndex(folder => folder.id === id)
|
||||||
const updatedSettings = { ...this.settings, ...newSettings }
|
if (index !== -1) {
|
||||||
this.settings = updatedSettings
|
this.folders[index] = updatedFolder
|
||||||
await storage.saveSettings(updatedSettings)
|
}
|
||||||
} catch (error) {
|
}
|
||||||
console.error('Error updating settings:', error)
|
return updatedFolder
|
||||||
throw error
|
} catch (error) {
|
||||||
}
|
console.error('Error updating folder:', error)
|
||||||
},
|
throw error
|
||||||
|
}
|
||||||
/**
|
},
|
||||||
* 切换云同步设置
|
|
||||||
* 开启或关闭云同步功能
|
/**
|
||||||
* @returns {Promise<void>}
|
* 删除文件夹
|
||||||
*/
|
* @param {string} id - 要删除的文件夹ID
|
||||||
async toggleCloudSync() {
|
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
||||||
await this.updateSettings({ cloudSync: !this.settings.cloudSync })
|
*/
|
||||||
},
|
async deleteFolder(id) {
|
||||||
|
try {
|
||||||
/**
|
const result = await storage.deleteFolder(id)
|
||||||
* 切换深色模式设置
|
if (result) {
|
||||||
* 开启或关闭深色模式
|
// 将文件夹中的便签移回"全部便签"
|
||||||
* @returns {Promise<void>}
|
const notesInFolder = this.notes.filter(note => note.folderId === id)
|
||||||
*/
|
for (const note of notesInFolder) {
|
||||||
async toggleDarkMode() {
|
await this.updateNote(note.id, { folderId: null })
|
||||||
await this.updateSettings({ darkMode: !this.settings.darkMode })
|
}
|
||||||
},
|
|
||||||
},
|
// 从文件夹列表中移除文件夹
|
||||||
})
|
this.folders = this.folders.filter(folder => folder.id !== id)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting folder:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置操作函数
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新设置
|
||||||
|
* @param {Object} newSettings - 新的设置对象
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async updateSettings(newSettings) {
|
||||||
|
try {
|
||||||
|
const updatedSettings = { ...this.settings, ...newSettings }
|
||||||
|
this.settings = updatedSettings
|
||||||
|
await storage.saveSettings(updatedSettings)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating settings:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换云同步设置
|
||||||
|
* 开启或关闭云同步功能
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async toggleCloudSync() {
|
||||||
|
await this.updateSettings({ cloudSync: !this.settings.cloudSync })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换深色模式设置
|
||||||
|
* 开启或关闭深色模式
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async toggleDarkMode() {
|
||||||
|
await this.updateSettings({ darkMode: !this.settings.darkMode })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,475 +1,510 @@
|
|||||||
import { getCurrentDateTime, getTimestamp } from './dateUtils'
|
import { getCurrentDateTime, getTimestamp } from './dateUtils'
|
||||||
|
|
||||||
// 数据库配置
|
// 数据库配置
|
||||||
const DB_NAME = 'SmartisanNoteDB'
|
const DB_NAME = 'SmartisanNoteDB'
|
||||||
const DB_VERSION = 2 // 更新版本号以确保数据库重新创建
|
const DB_VERSION = 2 // 更新版本号以确保数据库重新创建
|
||||||
const NOTES_STORE = 'notes'
|
const NOTES_STORE = 'notes'
|
||||||
const FOLDERS_STORE = 'folders'
|
const FOLDERS_STORE = 'folders'
|
||||||
const SETTINGS_STORE = 'settings'
|
const SETTINGS_STORE = 'settings'
|
||||||
|
|
||||||
let db = null
|
let db = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开数据库连接
|
* 打开数据库连接
|
||||||
* @returns {Promise<IDBDatabase>} 数据库实例
|
* @returns {Promise<IDBDatabase>} 数据库实例
|
||||||
*/
|
*/
|
||||||
const openDB = () => {
|
const openDB = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (db) {
|
if (db) {
|
||||||
return resolve(db)
|
return resolve(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = indexedDB.open(DB_NAME, DB_VERSION)
|
const request = indexedDB.open(DB_NAME, DB_VERSION)
|
||||||
|
|
||||||
request.onerror = () => {
|
request.onerror = () => {
|
||||||
reject(new Error('无法打开数据库'))
|
reject(new Error('无法打开数据库'))
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onsuccess = () => {
|
request.onsuccess = () => {
|
||||||
db = request.result
|
db = request.result
|
||||||
resolve(db)
|
resolve(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onupgradeneeded = (event) => {
|
request.onupgradeneeded = (event) => {
|
||||||
const database = event.target.result
|
const database = event.target.result
|
||||||
|
|
||||||
// 删除现有的对象存储(如果版本已更改)
|
// 删除现有的对象存储(如果版本已更改)
|
||||||
if (event.oldVersion > 0) {
|
if (event.oldVersion > 0) {
|
||||||
if (database.objectStoreNames.contains(NOTES_STORE)) {
|
if (database.objectStoreNames.contains(NOTES_STORE)) {
|
||||||
database.deleteObjectStore(NOTES_STORE)
|
database.deleteObjectStore(NOTES_STORE)
|
||||||
}
|
}
|
||||||
if (database.objectStoreNames.contains(FOLDERS_STORE)) {
|
if (database.objectStoreNames.contains(FOLDERS_STORE)) {
|
||||||
database.deleteObjectStore(FOLDERS_STORE)
|
database.deleteObjectStore(FOLDERS_STORE)
|
||||||
}
|
}
|
||||||
if (database.objectStoreNames.contains(SETTINGS_STORE)) {
|
if (database.objectStoreNames.contains(SETTINGS_STORE)) {
|
||||||
database.deleteObjectStore(SETTINGS_STORE)
|
database.deleteObjectStore(SETTINGS_STORE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建便签存储对象
|
// 创建便签存储对象
|
||||||
const notesStore = database.createObjectStore(NOTES_STORE, { keyPath: 'id' })
|
const notesStore = database.createObjectStore(NOTES_STORE, { keyPath: 'id' })
|
||||||
notesStore.createIndex('folderId', 'folderId', { unique: false })
|
notesStore.createIndex('folderId', 'folderId', { unique: false })
|
||||||
notesStore.createIndex('isStarred', 'isStarred', { unique: false })
|
notesStore.createIndex('isStarred', 'isStarred', { unique: false })
|
||||||
notesStore.createIndex('isDeleted', 'isDeleted', { unique: false })
|
notesStore.createIndex('isDeleted', 'isDeleted', { unique: false })
|
||||||
notesStore.createIndex('createdAt', 'createdAt', { unique: false })
|
notesStore.createIndex('createdAt', 'createdAt', { unique: false })
|
||||||
notesStore.createIndex('updatedAt', 'updatedAt', { unique: false })
|
notesStore.createIndex('updatedAt', 'updatedAt', { unique: false })
|
||||||
|
|
||||||
// 创建文件夹存储对象
|
// 创建文件夹存储对象
|
||||||
database.createObjectStore(FOLDERS_STORE, { keyPath: 'id' })
|
database.createObjectStore(FOLDERS_STORE, { keyPath: 'id' })
|
||||||
|
|
||||||
// 创建设置存储对象
|
// 创建设置存储对象
|
||||||
database.createObjectStore(SETTINGS_STORE)
|
database.createObjectStore(SETTINGS_STORE)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从存储中获取数据
|
* 从存储中获取数据
|
||||||
* @param {string} storeName - 存储名称
|
* @param {string} storeName - 存储名称
|
||||||
* @returns {Promise<Array>} 数据数组
|
* @returns {Promise<Array>} 数据数组
|
||||||
*/
|
*/
|
||||||
const getAllFromStore = async (storeName) => {
|
const getAllFromStore = async (storeName) => {
|
||||||
const database = await openDB()
|
const database = await openDB()
|
||||||
const transaction = database.transaction([storeName], 'readonly')
|
const transaction = database.transaction([storeName], 'readonly')
|
||||||
const store = transaction.objectStore(storeName)
|
const store = transaction.objectStore(storeName)
|
||||||
const request = store.getAll()
|
const request = store.getAll()
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request.onsuccess = () => {
|
request.onsuccess = () => {
|
||||||
resolve(request.result || [])
|
resolve(request.result || [])
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onerror = () => {
|
request.onerror = () => {
|
||||||
reject(new Error(`获取 ${storeName} 数据失败`))
|
reject(new Error(`获取 ${storeName} 数据失败`))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存数据到存储
|
* 保存数据到存储
|
||||||
* @param {string} storeName - 存储名称
|
* @param {string} storeName - 存储名称
|
||||||
* @param {Array} data - 要保存的数据数组
|
* @param {Array} data - 要保存的数据数组
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const saveToStore = async (storeName, data) => {
|
const saveToStore = async (storeName, data) => {
|
||||||
const database = await openDB()
|
const database = await openDB()
|
||||||
const transaction = database.transaction([storeName], 'readwrite')
|
const transaction = database.transaction([storeName], 'readwrite')
|
||||||
const store = transaction.objectStore(storeName)
|
const store = transaction.objectStore(storeName)
|
||||||
|
|
||||||
// 清除现有数据
|
// 清除现有数据
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
const clearRequest = store.clear()
|
const clearRequest = store.clear()
|
||||||
clearRequest.onsuccess = () => resolve()
|
clearRequest.onsuccess = () => resolve()
|
||||||
clearRequest.onerror = () => reject(new Error(`清除 ${storeName} 数据失败`))
|
clearRequest.onerror = () => reject(new Error(`清除 ${storeName} 数据失败`))
|
||||||
})
|
})
|
||||||
|
|
||||||
// 添加新数据
|
// 添加新数据
|
||||||
for (const item of data) {
|
for (const item of data) {
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
const addRequest = store.add(item)
|
const addRequest = store.add(item)
|
||||||
addRequest.onsuccess = () => resolve()
|
addRequest.onsuccess = () => resolve()
|
||||||
addRequest.onerror = () => reject(new Error(`保存 ${storeName} 数据失败`))
|
addRequest.onerror = () => reject(new Error(`保存 ${storeName} 数据失败`))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从存储中获取单个项
|
* 从存储中获取单个项
|
||||||
* @param {string} storeName - 存储名称
|
* @param {string} storeName - 存储名称
|
||||||
* @param {string} id - 项的ID
|
* @param {string} id - 项的ID
|
||||||
* @returns {Promise<Object|null>} 项对象或null
|
* @returns {Promise<Object|null>} 项对象或null
|
||||||
*/
|
*/
|
||||||
const getFromStore = async (storeName, id) => {
|
const getFromStore = async (storeName, id) => {
|
||||||
const database = await openDB()
|
const database = await openDB()
|
||||||
const transaction = database.transaction([storeName], 'readonly')
|
const transaction = database.transaction([storeName], 'readonly')
|
||||||
const store = transaction.objectStore(storeName)
|
const store = transaction.objectStore(storeName)
|
||||||
const request = store.get(id)
|
const request = store.get(id)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request.onsuccess = () => {
|
request.onsuccess = () => {
|
||||||
resolve(request.result || null)
|
resolve(request.result || null)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onerror = () => {
|
request.onerror = () => {
|
||||||
reject(new Error(`获取 ${storeName} 项失败`))
|
reject(new Error(`获取 ${storeName} 项失败`))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向存储中添加项
|
* 向存储中添加项
|
||||||
* @param {string} storeName - 存储名称
|
* @param {string} storeName - 存储名称
|
||||||
* @param {Object} item - 要添加的项
|
* @param {Object} item - 要添加的项
|
||||||
* @returns {Promise<Object>} 添加的项
|
* @returns {Promise<Object>} 添加的项
|
||||||
*/
|
*/
|
||||||
const addToStore = async (storeName, item) => {
|
const addToStore = async (storeName, item) => {
|
||||||
const database = await openDB()
|
const database = await openDB()
|
||||||
const transaction = database.transaction([storeName], 'readwrite')
|
const transaction = database.transaction([storeName], 'readwrite')
|
||||||
const store = transaction.objectStore(storeName)
|
const store = transaction.objectStore(storeName)
|
||||||
const request = store.add(item)
|
const request = store.add(item)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request.onsuccess = () => {
|
request.onsuccess = () => {
|
||||||
resolve(item)
|
resolve(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onerror = () => {
|
request.onerror = () => {
|
||||||
reject(new Error(`添加 ${storeName} 项失败`))
|
reject(new Error(`添加 ${storeName} 项失败`))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新存储中的项
|
* 更新存储中的项
|
||||||
* @param {string} storeName - 存储名称
|
* @param {string} storeName - 存储名称
|
||||||
* @param {string} id - 项的ID
|
* @param {string} id - 项的ID
|
||||||
* @param {Object} updates - 要更新的属性对象
|
* @param {Object} updates - 要更新的属性对象
|
||||||
* @returns {Promise<Object|null>} 更新后的项或null
|
* @returns {Promise<Object|null>} 更新后的项或null
|
||||||
*/
|
*/
|
||||||
const updateInStore = async (storeName, id, updates) => {
|
const updateInStore = async (storeName, id, updates) => {
|
||||||
const item = await getFromStore(storeName, id)
|
const item = await getFromStore(storeName, id)
|
||||||
if (!item) return null
|
if (!item) return null
|
||||||
|
|
||||||
const updatedItem = { ...item, ...updates }
|
const updatedItem = { ...item, ...updates }
|
||||||
const database = await openDB()
|
const database = await openDB()
|
||||||
const transaction = database.transaction([storeName], 'readwrite')
|
const transaction = database.transaction([storeName], 'readwrite')
|
||||||
const store = transaction.objectStore(storeName)
|
const store = transaction.objectStore(storeName)
|
||||||
const request = store.put(updatedItem)
|
const request = store.put(updatedItem)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request.onsuccess = () => {
|
request.onsuccess = () => {
|
||||||
resolve(updatedItem)
|
resolve(updatedItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onerror = () => {
|
request.onerror = () => {
|
||||||
reject(new Error(`更新 ${storeName} 项失败`))
|
reject(new Error(`更新 ${storeName} 项失败`))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从存储中删除项
|
* 从存储中删除项
|
||||||
* @param {string} storeName - 存储名称
|
* @param {string} storeName - 存储名称
|
||||||
* @param {string} id - 要删除的项的ID
|
* @param {string} id - 要删除的项的ID
|
||||||
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
||||||
*/
|
*/
|
||||||
const deleteFromStore = async (storeName, id) => {
|
const deleteFromStore = async (storeName, id) => {
|
||||||
const database = await openDB()
|
const database = await openDB()
|
||||||
const transaction = database.transaction([storeName], 'readwrite')
|
const transaction = database.transaction([storeName], 'readwrite')
|
||||||
const store = transaction.objectStore(storeName)
|
const store = transaction.objectStore(storeName)
|
||||||
const request = store.delete(id)
|
const request = store.delete(id)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request.onsuccess = () => {
|
request.onsuccess = () => {
|
||||||
resolve(true)
|
resolve(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onerror = () => {
|
request.onerror = () => {
|
||||||
reject(new Error(`删除 ${storeName} 项失败`))
|
reject(new Error(`删除 ${storeName} 项失败`))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 便签操作函数
|
// 便签操作函数
|
||||||
// 提供便签的增删改查功能
|
// 提供便签的增删改查功能
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有便签数据
|
* 获取所有便签数据
|
||||||
* 从IndexedDB中读取便签数据
|
* 从IndexedDB中读取便签数据
|
||||||
* @returns {Promise<Array>} 便签数组
|
* @returns {Promise<Array>} 便签数组
|
||||||
*/
|
*/
|
||||||
export const getNotes = async () => {
|
export const getNotes = async () => {
|
||||||
try {
|
try {
|
||||||
const notes = await getAllFromStore(NOTES_STORE)
|
const notes = await getAllFromStore(NOTES_STORE)
|
||||||
return ensureNotesDefaults(notes)
|
return ensureNotesDefaults(notes)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting notes:', error)
|
console.error('Error getting notes:', error)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存便签数据
|
* 保存便签数据
|
||||||
* 将便签数组保存到IndexedDB
|
* 将便签数组保存到IndexedDB
|
||||||
* @param {Array} notes - 便签数组
|
* @param {Array} notes - 便签数组
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export const saveNotes = async (notes) => {
|
export const saveNotes = async (notes) => {
|
||||||
try {
|
try {
|
||||||
await saveToStore(NOTES_STORE, notes)
|
await saveToStore(NOTES_STORE, notes)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving notes:', error)
|
console.error('Error saving notes:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加新便签
|
* 添加新便签
|
||||||
* 创建一个新的便签对象并添加到便签列表中
|
* 创建一个新的便签对象并添加到便签列表中
|
||||||
* @param {Object} note - 便签对象,包含便签内容和其他属性
|
* @param {Object} note - 便签对象,包含便签内容和其他属性
|
||||||
* @returns {Promise<Object>} 新创建的便签对象
|
* @returns {Promise<Object>} 新创建的便签对象
|
||||||
*/
|
*/
|
||||||
export const addNote = async (note) => {
|
export const addNote = async (note) => {
|
||||||
try {
|
try {
|
||||||
// 创建新的便签对象,添加必要的属性
|
// 创建新的便签对象,添加必要的属性
|
||||||
const newNote = {
|
const newNote = {
|
||||||
title: note.title || '',
|
title: note.title || '',
|
||||||
content: note.content || '',
|
content: note.content || '',
|
||||||
id: note.id || getTimestamp().toString(), // 使用时间戳生成唯一ID
|
id: note.id || getTimestamp().toString(), // 使用时间戳生成唯一ID
|
||||||
createdAt: note.createdAt || getCurrentDateTime(), // 创建时间
|
createdAt: note.createdAt || getCurrentDateTime(), // 创建时间
|
||||||
updatedAt: note.updatedAt || getCurrentDateTime(), // 更新时间
|
updatedAt: note.updatedAt || getCurrentDateTime(), // 更新时间
|
||||||
isStarred: note.isStarred || false, // 是否加星
|
isStarred: note.isStarred || false, // 是否加星
|
||||||
isTop: note.isTop || false, // 是否置顶
|
isTop: note.isTop || false, // 是否置顶
|
||||||
hasImage: note.hasImage || false, // 是否包含图片
|
hasImage: note.hasImage || false, // 是否包含图片
|
||||||
isDeleted: note.isDeleted || false, // 是否已删除
|
isDeleted: note.isDeleted || false, // 是否已删除
|
||||||
deletedAt: note.deletedAt || null, // 删除时间
|
deletedAt: note.deletedAt || null, // 删除时间
|
||||||
folderId: note.folderId || null, // 文件夹ID
|
folderId: note.folderId || null, // 文件夹ID
|
||||||
...note
|
...note
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加到存储
|
// 添加到存储
|
||||||
await addToStore(NOTES_STORE, newNote)
|
await addToStore(NOTES_STORE, newNote)
|
||||||
return newNote
|
return newNote
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding note:', error)
|
console.error('Error adding note:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新便签
|
* 更新便签
|
||||||
* 根据ID查找并更新便签信息
|
* 根据ID查找并更新便签信息
|
||||||
* @param {string} id - 便签ID
|
* @param {string} id - 便签ID
|
||||||
* @param {Object} updates - 要更新的属性对象
|
* @param {Object} updates - 要更新的属性对象
|
||||||
* @returns {Promise<Object|null>} 更新后的便签对象,如果未找到则返回null
|
* @returns {Promise<Object|null>} 更新后的便签对象,如果未找到则返回null
|
||||||
*/
|
*/
|
||||||
export const updateNote = async (id, updates) => {
|
export const updateNote = async (id, updates) => {
|
||||||
try {
|
try {
|
||||||
// 更新便签并保存
|
// 更新便签并保存
|
||||||
const updatedNote = await updateInStore(NOTES_STORE, id, {
|
const updatedNote = await updateInStore(NOTES_STORE, id, {
|
||||||
...updates,
|
...updates,
|
||||||
updatedAt: getCurrentDateTime() // 更新最后修改时间
|
updatedAt: getCurrentDateTime() // 更新最后修改时间
|
||||||
})
|
})
|
||||||
|
|
||||||
return updatedNote
|
return updatedNote
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating note:', error)
|
console.error('Error updating note:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除便签
|
* 删除便签
|
||||||
* 根据ID从便签列表中移除便签
|
* 根据ID从便签列表中移除便签
|
||||||
* @param {string} id - 要删除的便签ID
|
* @param {string} id - 要删除的便签ID
|
||||||
* @returns {Promise<boolean>} 删除成功返回true,未找到便签返回false
|
* @returns {Promise<boolean>} 删除成功返回true,未找到便签返回false
|
||||||
*/
|
*/
|
||||||
export const deleteNote = async (id) => {
|
export const deleteNote = async (id) => {
|
||||||
try {
|
try {
|
||||||
// 从存储中删除
|
// 从存储中删除
|
||||||
const result = await deleteFromStore(NOTES_STORE, id)
|
const result = await deleteFromStore(NOTES_STORE, id)
|
||||||
return result
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting note:', error)
|
console.error('Error deleting note:', error)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件夹操作函数
|
// 文件夹操作函数
|
||||||
// 提供文件夹的增删改查功能
|
// 提供文件夹的增删改查功能
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有文件夹数据
|
* 获取所有文件夹数据
|
||||||
* 从IndexedDB中读取文件夹数据
|
* 从IndexedDB中读取文件夹数据
|
||||||
* @returns {Promise<Array>} 文件夹数组
|
* @returns {Promise<Array>} 文件夹数组
|
||||||
*/
|
*/
|
||||||
export const getFolders = async () => {
|
export const getFolders = async () => {
|
||||||
try {
|
try {
|
||||||
const folders = await getAllFromStore(FOLDERS_STORE)
|
const folders = await getAllFromStore(FOLDERS_STORE)
|
||||||
return ensureFoldersDefaults(folders)
|
return ensureFoldersDefaults(folders)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting folders:', error)
|
console.error('Error getting folders:', error)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存文件夹数据
|
* 保存文件夹数据
|
||||||
* 将文件夹数组保存到IndexedDB
|
* 将文件夹数组保存到IndexedDB
|
||||||
* @param {Array} folders - 文件夹数组
|
* @param {Array} folders - 文件夹数组
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export const saveFolders = async (folders) => {
|
export const saveFolders = async (folders) => {
|
||||||
try {
|
try {
|
||||||
await saveToStore(FOLDERS_STORE, folders)
|
await saveToStore(FOLDERS_STORE, folders)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving folders:', error)
|
console.error('Error saving folders:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加新文件夹
|
* 添加新文件夹
|
||||||
* 创建一个新的文件夹对象并添加到文件夹列表中
|
* 创建一个新的文件夹对象并添加到文件夹列表中
|
||||||
* @param {Object} folder - 文件夹对象,包含文件夹名称等属性
|
* @param {Object} folder - 文件夹对象,包含文件夹名称等属性
|
||||||
* @returns {Promise<Object>} 新创建的文件夹对象
|
* @returns {Promise<Object>} 新创建的文件夹对象
|
||||||
*/
|
*/
|
||||||
export const addFolder = async (folder) => {
|
export const addFolder = async (folder) => {
|
||||||
try {
|
try {
|
||||||
// 创建新的文件夹对象,添加必要的属性
|
// 创建新的文件夹对象,添加必要的属性
|
||||||
const newFolder = {
|
const newFolder = {
|
||||||
name: folder.name || '',
|
name: folder.name || '',
|
||||||
id: folder.id || getTimestamp().toString(), // 使用时间戳生成唯一ID
|
id: folder.id || getTimestamp().toString(), // 使用时间戳生成唯一ID
|
||||||
createdAt: folder.createdAt || getCurrentDateTime(), // 创建时间
|
createdAt: folder.createdAt || getCurrentDateTime(), // 创建时间
|
||||||
...folder
|
...folder
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加到存储
|
// 添加到存储
|
||||||
await addToStore(FOLDERS_STORE, newFolder)
|
await addToStore(FOLDERS_STORE, newFolder)
|
||||||
return newFolder
|
return newFolder
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding folder:', error)
|
console.error('Error adding folder:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置操作函数
|
/**
|
||||||
// 提供应用设置的读取和保存功能
|
* 更新文件夹
|
||||||
|
* 根据ID查找并更新文件夹信息
|
||||||
/**
|
* @param {string} id - 文件夹ID
|
||||||
* 获取应用设置
|
* @param {Object} updates - 要更新的属性对象
|
||||||
* 从IndexedDB中读取设置数据
|
* @returns {Promise<Object|null>} 更新后的文件夹对象,如果未找到则返回null
|
||||||
* @returns {Promise<Object>} 设置对象,如果读取失败则返回默认设置
|
*/
|
||||||
*/
|
export const updateFolder = async (id, updates) => {
|
||||||
export const getSettings = async () => {
|
try {
|
||||||
try {
|
// 更新文件夹并保存
|
||||||
const database = await openDB()
|
const updatedFolder = await updateInStore(FOLDERS_STORE, id, updates)
|
||||||
const transaction = database.transaction([SETTINGS_STORE], 'readonly')
|
return updatedFolder
|
||||||
const store = transaction.objectStore(SETTINGS_STORE)
|
} catch (error) {
|
||||||
const request = store.get('settings')
|
console.error('Error updating folder:', error)
|
||||||
|
throw error
|
||||||
const settings = await new Promise((resolve, reject) => {
|
}
|
||||||
request.onsuccess = () => {
|
}
|
||||||
resolve(request.result || { cloudSync: false, darkMode: false })
|
|
||||||
}
|
/**
|
||||||
|
* 删除文件夹
|
||||||
request.onerror = () => {
|
* 根据ID从文件夹列表中移除文件夹
|
||||||
reject(new Error('获取设置失败'))
|
* @param {string} id - 要删除的文件夹ID
|
||||||
}
|
* @returns {Promise<boolean>} 删除成功返回true,未找到文件夹返回false
|
||||||
})
|
*/
|
||||||
|
export const deleteFolder = async (id) => {
|
||||||
return settings
|
try {
|
||||||
} catch (error) {
|
// 从存储中删除
|
||||||
console.error('Error getting settings:', error)
|
const result = await deleteFromStore(FOLDERS_STORE, id)
|
||||||
// 出错时返回默认设置
|
return result
|
||||||
return { cloudSync: false, darkMode: false }
|
} catch (error) {
|
||||||
}
|
console.error('Error deleting folder:', error)
|
||||||
}
|
return false
|
||||||
|
}
|
||||||
/**
|
}
|
||||||
* 保存应用设置
|
|
||||||
* 将设置对象保存到IndexedDB
|
// 设置操作函数
|
||||||
* @param {Object} settings - 设置对象
|
// 提供应用设置的读取和保存功能
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
/**
|
||||||
export const saveSettings = async (settings) => {
|
* 获取应用设置
|
||||||
try {
|
* 从IndexedDB中读取设置数据
|
||||||
const database = await openDB()
|
* @returns {Promise<Object>} 设置对象,如果读取失败则返回默认设置
|
||||||
const transaction = database.transaction([SETTINGS_STORE], 'readwrite')
|
*/
|
||||||
const store = transaction.objectStore(SETTINGS_STORE)
|
export const getSettings = async () => {
|
||||||
const request = store.put(settings, 'settings')
|
try {
|
||||||
|
const database = await openDB()
|
||||||
await new Promise((resolve, reject) => {
|
const transaction = database.transaction([SETTINGS_STORE], 'readonly')
|
||||||
request.onsuccess = () => resolve()
|
const store = transaction.objectStore(SETTINGS_STORE)
|
||||||
request.onerror = () => reject(new Error('保存设置失败'))
|
const request = store.get('settings')
|
||||||
})
|
|
||||||
} catch (error) {
|
const settings = await new Promise((resolve, reject) => {
|
||||||
console.error('Error saving settings:', error)
|
request.onsuccess = () => {
|
||||||
}
|
resolve(request.result || { cloudSync: false, darkMode: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
request.onerror = () => {
|
||||||
* 确保数据有默认值
|
reject(new Error('获取设置失败'))
|
||||||
* @param {Array} notes - 便签数组
|
}
|
||||||
* @returns {Array} 处理后的便签数组
|
})
|
||||||
*/
|
|
||||||
const ensureNotesDefaults = (notes) => {
|
return settings
|
||||||
return notes.map(note => ({
|
} catch (error) {
|
||||||
title: note.title || '',
|
console.error('Error getting settings:', error)
|
||||||
content: note.content || '',
|
// 出错时返回默认设置
|
||||||
id: note.id,
|
return { cloudSync: false, darkMode: false }
|
||||||
createdAt: note.createdAt,
|
}
|
||||||
updatedAt: note.updatedAt,
|
}
|
||||||
isStarred: note.isStarred || false,
|
|
||||||
isTop: note.isTop || false,
|
/**
|
||||||
hasImage: note.hasImage || false,
|
* 保存应用设置
|
||||||
isDeleted: note.isDeleted || false,
|
* 将设置对象保存到IndexedDB
|
||||||
deletedAt: note.deletedAt || null,
|
* @param {Object} settings - 设置对象
|
||||||
folderId: note.folderId || null,
|
* @returns {Promise<void>}
|
||||||
...note
|
*/
|
||||||
}))
|
export const saveSettings = async (settings) => {
|
||||||
}
|
try {
|
||||||
|
const database = await openDB()
|
||||||
/**
|
const transaction = database.transaction([SETTINGS_STORE], 'readwrite')
|
||||||
* 确保文件夹数据有默认值
|
const store = transaction.objectStore(SETTINGS_STORE)
|
||||||
* @param {Array} folders - 文件夹数组
|
const request = store.put(settings, 'settings')
|
||||||
* @returns {Array} 处理后的文件夹数组
|
|
||||||
*/
|
await new Promise((resolve, reject) => {
|
||||||
const ensureFoldersDefaults = (folders) => {
|
request.onsuccess = () => resolve()
|
||||||
return folders.map(folder => ({
|
request.onerror = () => reject(new Error('保存设置失败'))
|
||||||
name: folder.name || '',
|
})
|
||||||
id: folder.id,
|
} catch (error) {
|
||||||
createdAt: folder.createdAt,
|
console.error('Error saving settings:', error)
|
||||||
...folder
|
}
|
||||||
}))
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* 确保数据有默认值
|
||||||
* 初始化数据库
|
* @param {Array} notes - 便签数组
|
||||||
* @returns {Promise<void>}
|
* @returns {Array} 处理后的便签数组
|
||||||
*/
|
*/
|
||||||
export const initDB = async () => {
|
const ensureNotesDefaults = (notes) => {
|
||||||
try {
|
return notes.map(note => ({
|
||||||
await openDB()
|
title: note.title || '',
|
||||||
} catch (error) {
|
content: note.content || '',
|
||||||
console.error('Error initializing database:', error)
|
id: note.id,
|
||||||
}
|
createdAt: note.createdAt,
|
||||||
|
updatedAt: note.updatedAt,
|
||||||
|
isStarred: note.isStarred || false,
|
||||||
|
isTop: note.isTop || false,
|
||||||
|
hasImage: note.hasImage || false,
|
||||||
|
isDeleted: note.isDeleted || false,
|
||||||
|
deletedAt: note.deletedAt || null,
|
||||||
|
folderId: note.folderId || null,
|
||||||
|
...note
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保文件夹数据有默认值
|
||||||
|
* @param {Array} folders - 文件夹数组
|
||||||
|
* @returns {Array} 处理后的文件夹数组
|
||||||
|
*/
|
||||||
|
const ensureFoldersDefaults = (folders) => {
|
||||||
|
return folders.map(folder => ({
|
||||||
|
name: folder.name || '',
|
||||||
|
id: folder.id,
|
||||||
|
createdAt: folder.createdAt,
|
||||||
|
...folder
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化数据库
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export const initDB = async () => {
|
||||||
|
try {
|
||||||
|
await openDB()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing database:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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