You've already forked SmartisanNote.Remake
future #13
249
src/components/ConfirmDialog.vue
Normal file
249
src/components/ConfirmDialog.vue
Normal file
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div v-if="visible" class="pd-mask" @click="handleMaskClick">
|
||||
<div class="pd-confirm" @click.stop>
|
||||
<div class="pd-blur"></div>
|
||||
<div class="pd-plan"></div>
|
||||
<h2 class="pd-title" v-if="title">{{ title }}</h2>
|
||||
<p class="pd-message">{{ message }}</p>
|
||||
<div class="pd-buttons">
|
||||
<button v-if="showConfirm" class="pd-button pd-confirm-btn" @click="handleConfirm">
|
||||
{{ confirmText }}
|
||||
</button>
|
||||
<button v-if="showCancel" class="pd-button pd-cancel" @click="handleCancel">
|
||||
{{ cancelText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确认',
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消',
|
||||
},
|
||||
showConfirm: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showCancel: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['confirm', 'cancel', 'update:visible'])
|
||||
const visible = defineModel('visible', { type: Boolean, default: false })
|
||||
|
||||
// Promise 控制变量
|
||||
let resolvePromise, rejectPromise
|
||||
|
||||
// 返回 Promise 的方法,模拟原生 confirm 行为
|
||||
const show = (options = {}) => {
|
||||
// 更新 props 值
|
||||
Object.assign(props, options)
|
||||
|
||||
// 显示对话框
|
||||
visible.value = true
|
||||
emit('update:visible', true)
|
||||
|
||||
// 返回 Promise
|
||||
return new Promise((resolve, reject) => {
|
||||
resolvePromise = resolve
|
||||
rejectPromise = reject
|
||||
})
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
emit('confirm')
|
||||
visible.value = false
|
||||
emit('update:visible', false)
|
||||
|
||||
// 解决 Promise
|
||||
if (resolvePromise) {
|
||||
resolvePromise()
|
||||
resolvePromise = null
|
||||
rejectPromise = null
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('cancel')
|
||||
visible.value = false
|
||||
emit('update:visible', false)
|
||||
|
||||
// 拒绝 Promise
|
||||
if (rejectPromise) {
|
||||
rejectPromise()
|
||||
resolvePromise = null
|
||||
rejectPromise = null
|
||||
}
|
||||
}
|
||||
|
||||
const handleMaskClick = () => {
|
||||
if (props.maskClosable) {
|
||||
handleCancel() // 点击遮罩相当于取消
|
||||
}
|
||||
}
|
||||
|
||||
// 添加/移除 body 滚动锁定
|
||||
const lockBodyScroll = () => {
|
||||
document.body.style.overflow = 'hidden'
|
||||
}
|
||||
|
||||
const unlockBodyScroll = () => {
|
||||
document.body.style.overflow = ''
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (visible.value) {
|
||||
lockBodyScroll()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unlockBodyScroll()
|
||||
})
|
||||
|
||||
// 监听 visible 变化
|
||||
watch(visible, newVal => {
|
||||
if (newVal) {
|
||||
lockBodyScroll()
|
||||
} else {
|
||||
unlockBodyScroll()
|
||||
}
|
||||
})
|
||||
|
||||
// 暴露 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-blur,
|
||||
.pd-plan {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.pd-plan {
|
||||
background: inherit;
|
||||
background: rgba(255, 255, 255, 0.66);
|
||||
filter: blur(10px) saturate(2);
|
||||
}
|
||||
|
||||
.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-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,77 +1,168 @@
|
||||
<template>
|
||||
<div @click="onPress" class="code-fun-flex-row code-fun-items-center code-fun-relative folder-item">
|
||||
<img class="folder-icon" :src="iconSrc" />
|
||||
<span class="folder-name">{{ name }}</span>
|
||||
<span class="folder-count">{{ noteCount }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
noteCount: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
onPress: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
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'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style 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;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div @click="onPress" class="code-fun-flex-row code-fun-items-center code-fun-relative code-fun-justify-between folder-item">
|
||||
<div class="code-fun-flex-row code-fun-items-center">
|
||||
<!-- 文件夹图标或复选框 -->
|
||||
<div v-if="isSelectionMode && showDeleteButton" class="folder-checkbox" @click.stop="onCheckboxClick">
|
||||
<img :src="isChecked ? '/assets/icons/drawable-xxhdpi/check_box_on.png' : '/assets/icons/drawable-xxhdpi/check_box_off.png'" class="checkbox-icon" />
|
||||
</div>
|
||||
|
||||
<img v-else class="folder-icon" :src="iconSrc" />
|
||||
<span class="folder-name">{{ name }}</span>
|
||||
<span class="folder-count">{{ noteCount }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 编辑按钮,仅在选择模式下对自定义文件夹显示 -->
|
||||
<div v-if="showDeleteButton && isSelectionMode" class="folder-actions">
|
||||
<img class="edit-icon" src="/assets/icons/drawable-xxhdpi/icon_folder_rename.png" @click.stop="onEdit" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
noteCount: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
|
||||
onPress: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
|
||||
onEdit: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
|
||||
onDelete: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
isSelectionMode: {
|
||||
type: Boolean,
|
||||
|
||||
default: false,
|
||||
},
|
||||
|
||||
isChecked: {
|
||||
type: Boolean,
|
||||
|
||||
default: false,
|
||||
},
|
||||
|
||||
onCheckboxClick: {
|
||||
type: Function,
|
||||
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
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,554 @@
|
||||
<template>
|
||||
<div class="code-fun-flex-col page">
|
||||
<!-- 全部便签文件夹项 -->
|
||||
<FolderItem id="all" name="全部便签" :noteCount="allCount" :isSelected="selectedFolder === 'all'" :onPress="handleAllClick" />
|
||||
|
||||
<!-- 加星便签文件夹项 -->
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<div class="code-fun-flex-row code-fun-items-center">
|
||||
<img class="image_8 code-fun-ml-12" :src="`assets/icons/drawable-xxhdpi/btn_add_folder.png`" @click="handleAddFolder" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import FolderItem from './FolderItem.vue'
|
||||
|
||||
const props = defineProps({
|
||||
allCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
starredCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
trashCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
archiveCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
selectedFolder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
lastSyncTime: {
|
||||
type: String,
|
||||
default: '10/10上午9:28',
|
||||
},
|
||||
onAllClick: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
onStarredClick: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
onTrashClick: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
onArchiveClick: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
onAddFolder: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
const handleAllClick = () => {
|
||||
if (props.onAllClick) {
|
||||
props.onAllClick()
|
||||
}
|
||||
}
|
||||
|
||||
const handleStarredClick = () => {
|
||||
if (props.onStarredClick) {
|
||||
props.onStarredClick()
|
||||
}
|
||||
}
|
||||
|
||||
const handleTrashClick = () => {
|
||||
if (props.onTrashClick) {
|
||||
props.onTrashClick()
|
||||
}
|
||||
}
|
||||
|
||||
const handleArchiveClick = () => {
|
||||
if (props.onArchiveClick) {
|
||||
props.onArchiveClick()
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddFolder = event => {
|
||||
// 阻止事件冒泡到父元素
|
||||
event.stopPropagation()
|
||||
if (props.onAddFolder) {
|
||||
props.onAddFolder()
|
||||
}
|
||||
}
|
||||
</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: 2rem;
|
||||
height: 2rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
.text_11 {
|
||||
color: #cacaca;
|
||||
font-size: 0.7rem;
|
||||
line-height: 1.16rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="code-fun-flex-col page">
|
||||
<!-- 全部便签文件夹项 -->
|
||||
|
||||
<FolderItem id="all" name="全部便签" :noteCount="allCount" :isSelected="selectedFolder === 'all'" :onPress="handleAllClick" />
|
||||
|
||||
<!-- 加星便签文件夹项 -->
|
||||
|
||||
<FolderItem id="starred" name="加星便签" :noteCount="starredCount" :isSelected="selectedFolder === 'starred'" :onPress="handleStarredClick" />
|
||||
|
||||
<!-- 回收站文件夹项 -->
|
||||
|
||||
<FolderItem id="trash" name="回收站" :noteCount="trashCount" :isSelected="selectedFolder === 'trash'" :onPress="handleTrashClick" />
|
||||
|
||||
<!-- 自定义文件夹项 -->
|
||||
|
||||
<FolderItem
|
||||
v-for="folder in customFolders"
|
||||
:key="folder.id"
|
||||
:id="folder.id"
|
||||
:name="folder.name"
|
||||
:noteCount="getFolderNoteCount(folder.id)"
|
||||
:isSelected="selectedFolder === folder.id"
|
||||
:onPress="() => handleFolderClick(folder.id)"
|
||||
:onEdit="() => handleEditFolder(folder.id)"
|
||||
:onDelete="() => handleDeleteFolder(folder.id)"
|
||||
:isSelectionMode="isSelectionMode"
|
||||
:isChecked="selectedFolders.includes(folder.id)"
|
||||
:onCheckboxClick="() => toggleFolderSelection(folder.id)" />
|
||||
|
||||
<div class="code-fun-flex-col code-fun-justify-start section_7">
|
||||
<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="isSelectionMode ? 'assets/icons/drawable-xxhdpi/btn_back_black.png' : 'assets/icons/drawable-xxhdpi/btn_edit_folder.png'" @click="toggleSelectionMode" />
|
||||
|
||||
<!-- 同步信息区域 -->
|
||||
|
||||
<span class="text_11 code-fun-ml-10">上次同步:{{ lastSyncTime }}</span>
|
||||
</div>
|
||||
|
||||
<div class="code-fun-flex-row code-fun-items-center">
|
||||
<!-- 新建文件夹按钮 / 删除按钮 -->
|
||||
|
||||
<img
|
||||
class="image_8 code-fun-ml-12"
|
||||
:src="isSelectionMode ? 'assets/icons/drawable-xxhdpi/icon_folder_trash.png' : 'assets/icons/drawable-xxhdpi/btn_add_folder.png'"
|
||||
@click="isSelectionMode ? handleDeleteSelectedFolders() : handleAddFolder" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加文件夹的模态框 -->
|
||||
|
||||
<div v-if="showAddFolderModal" class="modal-overlay" @click="showAddFolderModal = false">
|
||||
<div class="modal-content" @click.stop>
|
||||
<h3>添加文件夹</h3>
|
||||
|
||||
<input v-model="newFolderName" placeholder="请输入文件夹名称" class="folder-input" @keyup.enter="confirmAddFolder" ref="folderInput" />
|
||||
|
||||
<div class="modal-actions">
|
||||
<button @click="showAddFolderModal = false" class="cancel-btn">取消</button>
|
||||
|
||||
<button @click="confirmAddFolder" class="confirm-btn">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 重命名文件夹的模态框 -->
|
||||
|
||||
<div v-if="showRenameFolderModal" class="modal-overlay" @click="showRenameFolderModal = false">
|
||||
<div class="modal-content" @click.stop>
|
||||
<h3>重命名文件夹</h3>
|
||||
|
||||
<input v-model="renameFolderName" placeholder="请输入文件夹名称" class="folder-input" @keyup.enter="confirmRenameFolder" ref="folderInput" />
|
||||
|
||||
<div class="modal-actions">
|
||||
<button @click="showRenameFolderModal = false" class="cancel-btn">取消</button>
|
||||
|
||||
<button @click="confirmRenameFolder" class="confirm-btn">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 确认删除文件夹的模态框 -->
|
||||
|
||||
<div v-if="showDeleteConfirmModal" class="modal-overlay" @click="showDeleteConfirmModal = false">
|
||||
<div class="modal-content" @click.stop>
|
||||
<h3>删除文件夹</h3>
|
||||
|
||||
<p>确定要删除选中的 {{ selectedFolders.length }} 个文件夹吗?文件夹中的便签将移至"全部便签"。</p>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button @click="showDeleteConfirmModal = false" class="cancel-btn">取消</button>
|
||||
|
||||
<button @click="confirmDeleteSelectedFolders" class="confirm-btn">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
|
||||
import { useAppStore } from '../stores/useAppStore'
|
||||
|
||||
import FolderItem from './FolderItem.vue'
|
||||
|
||||
const store = useAppStore()
|
||||
|
||||
const props = defineProps({
|
||||
allCount: {
|
||||
type: Number,
|
||||
|
||||
default: 0,
|
||||
},
|
||||
|
||||
starredCount: {
|
||||
type: Number,
|
||||
|
||||
default: 0,
|
||||
},
|
||||
|
||||
trashCount: {
|
||||
type: Number,
|
||||
|
||||
default: 0,
|
||||
},
|
||||
|
||||
archiveCount: {
|
||||
type: Number,
|
||||
|
||||
default: 0,
|
||||
},
|
||||
|
||||
selectedFolder: {
|
||||
type: String,
|
||||
|
||||
default: '',
|
||||
},
|
||||
|
||||
lastSyncTime: {
|
||||
type: String,
|
||||
|
||||
default: '10/10上午9:28',
|
||||
},
|
||||
|
||||
onAllClick: {
|
||||
type: Function,
|
||||
|
||||
default: null,
|
||||
},
|
||||
|
||||
onStarredClick: {
|
||||
type: Function,
|
||||
|
||||
default: null,
|
||||
},
|
||||
|
||||
onTrashClick: {
|
||||
type: Function,
|
||||
|
||||
default: null,
|
||||
},
|
||||
|
||||
onArchiveClick: {
|
||||
type: Function,
|
||||
|
||||
default: null,
|
||||
},
|
||||
|
||||
onAddFolder: {
|
||||
type: Function,
|
||||
|
||||
default: null,
|
||||
},
|
||||
|
||||
onFolderClick: {
|
||||
type: Function,
|
||||
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
// 添加文件夹相关状态
|
||||
|
||||
const showAddFolderModal = ref(false)
|
||||
|
||||
const newFolderName = ref('')
|
||||
|
||||
const folderInput = ref(null)
|
||||
|
||||
// 重命名文件夹相关状态
|
||||
|
||||
const showRenameFolderModal = ref(false)
|
||||
|
||||
const renameFolderId = ref(null)
|
||||
|
||||
const renameFolderName = ref('')
|
||||
|
||||
// 选择模式相关状态
|
||||
|
||||
const isSelectionMode = ref(false)
|
||||
|
||||
const selectedFolders = ref([])
|
||||
|
||||
const showDeleteConfirmModal = ref(false)
|
||||
|
||||
// 计算自定义文件夹(排除系统文件夹)
|
||||
|
||||
const customFolders = computed(() => {
|
||||
return store.folders.filter(folder => !['all', 'starred', 'trash', 'archive'].includes(folder.id))
|
||||
})
|
||||
|
||||
// 获取文件夹中的便签数量
|
||||
|
||||
const getFolderNoteCount = folderId => {
|
||||
return store.notes.filter(note => note.folderId === folderId && !note.isDeleted).length
|
||||
}
|
||||
|
||||
// 切换选择模式
|
||||
|
||||
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 = () => {
|
||||
if (selectedFolders.value.length === 0) return
|
||||
|
||||
showDeleteConfirmModal.value = true
|
||||
}
|
||||
|
||||
// 确认删除选中的文件夹
|
||||
|
||||
const confirmDeleteSelectedFolders = async () => {
|
||||
try {
|
||||
// 删除选中的文件夹
|
||||
|
||||
for (const folderId of selectedFolders.value) {
|
||||
// 跳过系统文件夹
|
||||
|
||||
if (['all', 'starred', 'trash', 'archive'].includes(folderId)) continue
|
||||
|
||||
await store.deleteFolder(folderId)
|
||||
}
|
||||
|
||||
// 清空选中项并退出选择模式
|
||||
|
||||
selectedFolders.value = []
|
||||
|
||||
isSelectionMode.value = false
|
||||
|
||||
showDeleteConfirmModal.value = false
|
||||
} catch (error) {
|
||||
console.error('删除文件夹失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理删除文件夹
|
||||
|
||||
const handleDeleteFolder = folderId => {
|
||||
// 阻止事件冒泡到父元素
|
||||
|
||||
event.stopPropagation()
|
||||
|
||||
// 确认删除
|
||||
|
||||
if (confirm(`确定要删除文件夹 "${getFolderName(folderId)}" 吗?文件夹中的便签将移至"全部便签"。`)) {
|
||||
try {
|
||||
store.deleteFolder(folderId)
|
||||
} catch (error) {
|
||||
console.error('删除文件夹失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理编辑文件夹
|
||||
|
||||
const handleEditFolder = folderId => {
|
||||
// 阻止事件冒泡到父元素
|
||||
|
||||
event.stopPropagation()
|
||||
|
||||
const folder = store.folders.find(f => f.id === folderId)
|
||||
|
||||
if (folder) {
|
||||
renameFolderId.value = folderId
|
||||
|
||||
renameFolderName.value = folder.name
|
||||
|
||||
showRenameFolderModal.value = true
|
||||
|
||||
// 在下次DOM更新后聚焦输入框
|
||||
|
||||
nextTick(() => {
|
||||
if (folderInput.value) {
|
||||
folderInput.value.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 确认重命名文件夹
|
||||
|
||||
const confirmRenameFolder = async () => {
|
||||
if (renameFolderName.value.trim() && renameFolderId.value) {
|
||||
try {
|
||||
await store.updateFolder(renameFolderId.value, { name: renameFolderName.value.trim() })
|
||||
|
||||
showRenameFolderModal.value = false
|
||||
|
||||
renameFolderId.value = null
|
||||
|
||||
renameFolderName.value = ''
|
||||
} catch (error) {
|
||||
console.error('重命名文件夹失败:', 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 handleArchiveClick = () => {
|
||||
if (isSelectionMode.value) {
|
||||
toggleFolderSelection('archive')
|
||||
} else {
|
||||
if (props.onArchiveClick) {
|
||||
props.onArchiveClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddFolder = event => {
|
||||
// 阻止事件冒泡到父元素
|
||||
|
||||
event.stopPropagation()
|
||||
|
||||
showAddFolderModal.value = true
|
||||
|
||||
newFolderName.value = ''
|
||||
|
||||
// 在下次DOM更新后聚焦输入框
|
||||
|
||||
nextTick(() => {
|
||||
if (folderInput.value) {
|
||||
folderInput.value.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const confirmAddFolder = async () => {
|
||||
if (newFolderName.value.trim()) {
|
||||
try {
|
||||
const newFolder = {
|
||||
name: newFolderName.value.trim(),
|
||||
|
||||
id: `folder_${Date.now()}`, // 生成唯一ID
|
||||
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
await store.addFolder(newFolder)
|
||||
|
||||
showAddFolderModal.value = false
|
||||
|
||||
newFolderName.value = ''
|
||||
} catch (error) {
|
||||
console.error('添加文件夹失败:', 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
width: 80%;
|
||||
max-width: 300px;
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.folder-input {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 0.3rem;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
padding: 0.8rem;
|
||||
border: none;
|
||||
border-radius: 0.3rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
margin: 0 0.5rem;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
|
||||
&:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #4a90e2;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background-color: #357ae8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
:selectedFolder="currentFolder"
|
||||
:onAllClick="handleAllNotesClick"
|
||||
:onStarredClick="handleStarredNotesClick"
|
||||
:onTrashClick="handleTrashNotesClick" />
|
||||
:onTrashClick="handleTrashNotesClick"
|
||||
:onFolderClick="handleFolderClick"
|
||||
:onAddFolder="handleAddFolder" />
|
||||
</div>
|
||||
</transition>
|
||||
<!-- 点击外部区域收起文件夹列表的覆盖层 -->
|
||||
@@ -168,7 +170,9 @@ const headerTitle = computed(() => {
|
||||
case 'trash':
|
||||
return '回收站'
|
||||
default:
|
||||
return '全部便签'
|
||||
// 查找自定义文件夹的名称
|
||||
const folder = store.folders.find(f => f.id === currentFolder.value)
|
||||
return folder ? folder.name : '全部便签'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -279,6 +283,17 @@ const handleTrashNotesClick = () => {
|
||||
setIsFolderExpanded(false)
|
||||
}
|
||||
|
||||
const handleFolderClick = (folderId) => {
|
||||
setCurrentFolder(folderId)
|
||||
setIsFolderExpanded(false)
|
||||
}
|
||||
|
||||
const handleAddFolder = () => {
|
||||
// 文件夹添加功能已在FolderManage组件中实现
|
||||
// 这里只需关闭文件夹列表
|
||||
setIsFolderExpanded(false)
|
||||
}
|
||||
|
||||
const handleFolderPress = () => {
|
||||
// 使用vue-router导航到文件夹页面
|
||||
router.push('/folders')
|
||||
|
||||
@@ -1,336 +1,383 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import * as storage from '../utils/indexedDBStorage'
|
||||
import { getCurrentDateTime, getPastDate } from '../utils/dateUtils'
|
||||
|
||||
/**
|
||||
* 应用状态管理Store
|
||||
* 使用Pinia进行状态管理,包含便签、文件夹和设置数据
|
||||
*/
|
||||
export const useAppStore = defineStore('app', {
|
||||
/**
|
||||
* 状态定义
|
||||
* 包含应用的核心数据:便签列表、文件夹列表和设置
|
||||
*/
|
||||
state: () => ({
|
||||
notes: [], // 便签列表
|
||||
folders: [], // 文件夹列表
|
||||
settings: { cloudSync: false, darkMode: false }, // 应用设置
|
||||
}),
|
||||
|
||||
/**
|
||||
* 计算属性
|
||||
* 基于状态派生的计算值
|
||||
*/
|
||||
getters: {
|
||||
/**
|
||||
* 计算加星便签数量
|
||||
* @param {Object} state - 当前状态对象
|
||||
* @returns {number} 加星便签的数量
|
||||
*/
|
||||
starredNotesCount: state => {
|
||||
return state.notes.filter(note => note.isStarred).length
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算所有便签数量
|
||||
* @param {Object} state - 当前状态对象
|
||||
* @returns {number} 所有便签的数量
|
||||
*/
|
||||
allNotesCount: state => {
|
||||
return state.notes.length
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 状态变更操作
|
||||
* 包含所有修改状态的方法
|
||||
*/
|
||||
actions: {
|
||||
/**
|
||||
* 初始化数据
|
||||
* 从Storage加载便签、文件夹和设置数据
|
||||
* 如果没有数据则加载预设的mock数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadData() {
|
||||
try {
|
||||
// 从Storage加载数据
|
||||
const loadedNotes = await storage.getNotes()
|
||||
const loadedFolders = await storage.getFolders()
|
||||
const loadedSettings = await storage.getSettings()
|
||||
|
||||
// 如果没有数据,则加载mock数据
|
||||
if (loadedNotes.length === 0 && loadedFolders.length === 0) {
|
||||
this.loadMockData()
|
||||
} else {
|
||||
// 否则使用加载的数据
|
||||
this.notes = loadedNotes
|
||||
this.folders = loadedFolders
|
||||
this.settings = loadedSettings
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载预设的mock数据
|
||||
* 用于开发和演示目的,提供示例便签、文件夹和设置
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadMockData() {
|
||||
// Mock notes - 使用固定的日期值以避免每次运行时变化
|
||||
const fixedCurrentDate = '2025-10-12T10:00:00.000Z';
|
||||
|
||||
// 预设的便签示例数据 - 仅保留一条关于应用功能介绍和示例的便签
|
||||
const mockNotes = [
|
||||
{
|
||||
id: '1',
|
||||
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>',
|
||||
createdAt: fixedCurrentDate,
|
||||
updatedAt: fixedCurrentDate,
|
||||
folderId: null,
|
||||
isStarred: true, // 加星便签
|
||||
isTop: true, // 置顶便签
|
||||
hasImage: true, // 包含图片
|
||||
isDeleted: false, // 未删除
|
||||
deletedAt: null,
|
||||
}
|
||||
]
|
||||
|
||||
// Mock folders - 使用固定的日期值
|
||||
// 预设的文件夹示例数据
|
||||
const mockFolders = [
|
||||
{
|
||||
id: 'folder1',
|
||||
name: '工作',
|
||||
createdAt: '2025-10-12T10:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'folder2',
|
||||
name: '个人',
|
||||
createdAt: '2025-10-12T10:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'folder3',
|
||||
name: '学习',
|
||||
createdAt: '2025-10-12T10:00:00.000Z',
|
||||
},
|
||||
]
|
||||
|
||||
// Mock settings
|
||||
// 预设的设置示例数据
|
||||
const mockSettings = {
|
||||
cloudSync: false, // 云同步关闭
|
||||
darkMode: false, // 深色模式关闭
|
||||
}
|
||||
|
||||
// 更新store状态
|
||||
this.notes = mockNotes
|
||||
this.folders = mockFolders
|
||||
this.settings = mockSettings
|
||||
|
||||
// 保存到Storage
|
||||
await storage.saveNotes(mockNotes)
|
||||
await storage.saveFolders(mockFolders)
|
||||
await storage.saveSettings(mockSettings)
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存便签数据到Storage
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async saveNotes() {
|
||||
try {
|
||||
await storage.saveNotes(this.notes)
|
||||
} catch (error) {
|
||||
console.error('Error saving notes:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存文件夹数据到Storage
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async saveFolders() {
|
||||
try {
|
||||
await storage.saveFolders(this.folders)
|
||||
} catch (error) {
|
||||
console.error('Error saving folders:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存设置数据到Storage
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async saveSettings() {
|
||||
try {
|
||||
await storage.saveSettings(this.settings)
|
||||
} catch (error) {
|
||||
console.error('Error saving settings:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 便签操作函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 添加新便签
|
||||
* @param {Object} note - 便签对象
|
||||
* @returns {Promise<Object>} 新创建的便签对象
|
||||
*/
|
||||
async addNote(note) {
|
||||
try {
|
||||
const newNote = await storage.addNote(note)
|
||||
this.notes.push(newNote)
|
||||
|
||||
return newNote
|
||||
} catch (error) {
|
||||
console.error('Error adding note:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新便签
|
||||
* @param {string} id - 便签ID
|
||||
* @param {Object} updates - 要更新的属性对象
|
||||
* @returns {Promise<Object>} 更新后的便签对象
|
||||
*/
|
||||
async updateNote(id, updates) {
|
||||
try {
|
||||
const updatedNote = await storage.updateNote(id, updates)
|
||||
if (updatedNote) {
|
||||
const index = this.notes.findIndex(note => note.id === id)
|
||||
if (index !== -1) {
|
||||
this.notes[index] = updatedNote
|
||||
}
|
||||
}
|
||||
return updatedNote
|
||||
} catch (error) {
|
||||
console.error('Error updating note:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除便签
|
||||
* @param {string} id - 要删除的便签ID
|
||||
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
||||
*/
|
||||
async deleteNote(id) {
|
||||
try {
|
||||
const result = await storage.deleteNote(id)
|
||||
if (result) {
|
||||
this.notes = this.notes.filter(note => note.id !== id)
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Error deleting note:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 将便签移至回收站
|
||||
* 将便签标记为已删除状态,并记录删除时间
|
||||
* @param {string} id - 便签ID
|
||||
* @returns {Promise<Object>} 更新后的便签对象
|
||||
*/
|
||||
async moveToTrash(id) {
|
||||
try {
|
||||
const updatedNote = await storage.updateNote(id, { isDeleted: true, deletedAt: getCurrentDateTime() })
|
||||
if (updatedNote) {
|
||||
const index = this.notes.findIndex(note => note.id === id)
|
||||
if (index !== -1) {
|
||||
this.notes[index] = updatedNote
|
||||
}
|
||||
}
|
||||
return updatedNote
|
||||
} catch (error) {
|
||||
console.error('Error moving note to trash:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 永久删除便签
|
||||
* 从便签列表中彻底移除便签
|
||||
* @param {string} id - 便签ID
|
||||
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
||||
*/
|
||||
async permanentlyDeleteNote(id) {
|
||||
try {
|
||||
const result = await storage.deleteNote(id)
|
||||
if (result) {
|
||||
this.notes = this.notes.filter(note => note.id !== id)
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Error permanently deleting note:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 文件夹操作函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 添加新文件夹
|
||||
* @param {Object} folder - 文件夹对象
|
||||
* @returns {Promise<Object>} 新创建的文件夹对象
|
||||
*/
|
||||
async addFolder(folder) {
|
||||
try {
|
||||
const newFolder = await storage.addFolder(folder)
|
||||
this.folders.push(newFolder)
|
||||
return newFolder
|
||||
} catch (error) {
|
||||
console.error('Error adding 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 })
|
||||
},
|
||||
},
|
||||
})
|
||||
import { defineStore } from 'pinia'
|
||||
import * as storage from '../utils/indexedDBStorage'
|
||||
import { getCurrentDateTime, getPastDate } from '../utils/dateUtils'
|
||||
|
||||
/**
|
||||
* 应用状态管理Store
|
||||
* 使用Pinia进行状态管理,包含便签、文件夹和设置数据
|
||||
*/
|
||||
export const useAppStore = defineStore('app', {
|
||||
/**
|
||||
* 状态定义
|
||||
* 包含应用的核心数据:便签列表、文件夹列表和设置
|
||||
*/
|
||||
state: () => ({
|
||||
notes: [], // 便签列表
|
||||
folders: [], // 文件夹列表
|
||||
settings: { cloudSync: false, darkMode: false }, // 应用设置
|
||||
}),
|
||||
|
||||
/**
|
||||
* 计算属性
|
||||
* 基于状态派生的计算值
|
||||
*/
|
||||
getters: {
|
||||
/**
|
||||
* 计算加星便签数量
|
||||
* @param {Object} state - 当前状态对象
|
||||
* @returns {number} 加星便签的数量
|
||||
*/
|
||||
starredNotesCount: state => {
|
||||
return state.notes.filter(note => note.isStarred).length
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算所有便签数量
|
||||
* @param {Object} state - 当前状态对象
|
||||
* @returns {number} 所有便签的数量
|
||||
*/
|
||||
allNotesCount: state => {
|
||||
return state.notes.length
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 状态变更操作
|
||||
* 包含所有修改状态的方法
|
||||
*/
|
||||
actions: {
|
||||
/**
|
||||
* 初始化数据
|
||||
* 从Storage加载便签、文件夹和设置数据
|
||||
* 如果没有数据则加载预设的mock数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadData() {
|
||||
try {
|
||||
// 从Storage加载数据
|
||||
const loadedNotes = await storage.getNotes()
|
||||
const loadedFolders = await storage.getFolders()
|
||||
const loadedSettings = await storage.getSettings()
|
||||
|
||||
// 如果没有数据,则加载mock数据
|
||||
if (loadedNotes.length === 0 && loadedFolders.length === 0) {
|
||||
this.loadMockData()
|
||||
} else {
|
||||
// 否则使用加载的数据
|
||||
this.notes = loadedNotes
|
||||
this.folders = loadedFolders
|
||||
this.settings = loadedSettings
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载预设的mock数据
|
||||
* 用于开发和演示目的,提供示例便签、文件夹和设置
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadMockData() {
|
||||
// Mock notes - 使用固定的日期值以避免每次运行时变化
|
||||
const fixedCurrentDate = '2025-10-12T10:00:00.000Z';
|
||||
|
||||
// 预设的便签示例数据 - 仅保留一条关于应用功能介绍和示例的便签
|
||||
const mockNotes = [
|
||||
{
|
||||
id: '1',
|
||||
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>',
|
||||
createdAt: fixedCurrentDate,
|
||||
updatedAt: fixedCurrentDate,
|
||||
folderId: null,
|
||||
isStarred: true, // 加星便签
|
||||
isTop: true, // 置顶便签
|
||||
hasImage: true, // 包含图片
|
||||
isDeleted: false, // 未删除
|
||||
deletedAt: null,
|
||||
}
|
||||
]
|
||||
|
||||
// Mock folders - 使用固定的日期值
|
||||
// 预设的文件夹示例数据
|
||||
const mockFolders = [
|
||||
{
|
||||
id: 'folder1',
|
||||
name: '工作',
|
||||
createdAt: '2025-10-12T10:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'folder2',
|
||||
name: '个人',
|
||||
createdAt: '2025-10-12T10:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'folder3',
|
||||
name: '学习',
|
||||
createdAt: '2025-10-12T10:00:00.000Z',
|
||||
},
|
||||
]
|
||||
|
||||
// Mock settings
|
||||
// 预设的设置示例数据
|
||||
const mockSettings = {
|
||||
cloudSync: false, // 云同步关闭
|
||||
darkMode: false, // 深色模式关闭
|
||||
}
|
||||
|
||||
// 更新store状态
|
||||
this.notes = mockNotes
|
||||
this.folders = mockFolders
|
||||
this.settings = mockSettings
|
||||
|
||||
// 保存到Storage
|
||||
await storage.saveNotes(mockNotes)
|
||||
await storage.saveFolders(mockFolders)
|
||||
await storage.saveSettings(mockSettings)
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存便签数据到Storage
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async saveNotes() {
|
||||
try {
|
||||
await storage.saveNotes(this.notes)
|
||||
} catch (error) {
|
||||
console.error('Error saving notes:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存文件夹数据到Storage
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async saveFolders() {
|
||||
try {
|
||||
await storage.saveFolders(this.folders)
|
||||
} catch (error) {
|
||||
console.error('Error saving folders:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存设置数据到Storage
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async saveSettings() {
|
||||
try {
|
||||
await storage.saveSettings(this.settings)
|
||||
} catch (error) {
|
||||
console.error('Error saving settings:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 便签操作函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 添加新便签
|
||||
* @param {Object} note - 便签对象
|
||||
* @returns {Promise<Object>} 新创建的便签对象
|
||||
*/
|
||||
async addNote(note) {
|
||||
try {
|
||||
const newNote = await storage.addNote(note)
|
||||
this.notes.push(newNote)
|
||||
|
||||
return newNote
|
||||
} catch (error) {
|
||||
console.error('Error adding note:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新便签
|
||||
* @param {string} id - 便签ID
|
||||
* @param {Object} updates - 要更新的属性对象
|
||||
* @returns {Promise<Object>} 更新后的便签对象
|
||||
*/
|
||||
async updateNote(id, updates) {
|
||||
try {
|
||||
const updatedNote = await storage.updateNote(id, updates)
|
||||
if (updatedNote) {
|
||||
const index = this.notes.findIndex(note => note.id === id)
|
||||
if (index !== -1) {
|
||||
this.notes[index] = updatedNote
|
||||
}
|
||||
}
|
||||
return updatedNote
|
||||
} catch (error) {
|
||||
console.error('Error updating note:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除便签
|
||||
* @param {string} id - 要删除的便签ID
|
||||
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
||||
*/
|
||||
async deleteNote(id) {
|
||||
try {
|
||||
const result = await storage.deleteNote(id)
|
||||
if (result) {
|
||||
this.notes = this.notes.filter(note => note.id !== id)
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Error deleting note:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 将便签移至回收站
|
||||
* 将便签标记为已删除状态,并记录删除时间
|
||||
* @param {string} id - 便签ID
|
||||
* @returns {Promise<Object>} 更新后的便签对象
|
||||
*/
|
||||
async moveToTrash(id) {
|
||||
try {
|
||||
const updatedNote = await storage.updateNote(id, { isDeleted: true, deletedAt: getCurrentDateTime() })
|
||||
if (updatedNote) {
|
||||
const index = this.notes.findIndex(note => note.id === id)
|
||||
if (index !== -1) {
|
||||
this.notes[index] = updatedNote
|
||||
}
|
||||
}
|
||||
return updatedNote
|
||||
} catch (error) {
|
||||
console.error('Error moving note to trash:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 永久删除便签
|
||||
* 从便签列表中彻底移除便签
|
||||
* @param {string} id - 便签ID
|
||||
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
||||
*/
|
||||
async permanentlyDeleteNote(id) {
|
||||
try {
|
||||
const result = await storage.deleteNote(id)
|
||||
if (result) {
|
||||
this.notes = this.notes.filter(note => note.id !== id)
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Error permanently deleting note:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 文件夹操作函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 添加新文件夹
|
||||
* @param {Object} folder - 文件夹对象
|
||||
* @returns {Promise<Object>} 新创建的文件夹对象
|
||||
*/
|
||||
async addFolder(folder) {
|
||||
try {
|
||||
const newFolder = await storage.addFolder(folder)
|
||||
this.folders.push(newFolder)
|
||||
return newFolder
|
||||
} catch (error) {
|
||||
console.error('Error adding folder:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新文件夹
|
||||
* @param {string} id - 文件夹ID
|
||||
* @param {Object} updates - 要更新的属性对象
|
||||
* @returns {Promise<Object>} 更新后的文件夹对象
|
||||
*/
|
||||
async updateFolder(id, updates) {
|
||||
try {
|
||||
const updatedFolder = await storage.updateFolder(id, updates)
|
||||
if (updatedFolder) {
|
||||
const index = this.folders.findIndex(folder => folder.id === id)
|
||||
if (index !== -1) {
|
||||
this.folders[index] = updatedFolder
|
||||
}
|
||||
}
|
||||
return updatedFolder
|
||||
} catch (error) {
|
||||
console.error('Error updating folder:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除文件夹
|
||||
* @param {string} id - 要删除的文件夹ID
|
||||
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
||||
*/
|
||||
async deleteFolder(id) {
|
||||
try {
|
||||
const result = await storage.deleteFolder(id)
|
||||
if (result) {
|
||||
// 将文件夹中的便签移回"全部便签"
|
||||
const notesInFolder = this.notes.filter(note => note.folderId === id)
|
||||
for (const note of notesInFolder) {
|
||||
await this.updateNote(note.id, { folderId: null })
|
||||
}
|
||||
|
||||
// 从文件夹列表中移除文件夹
|
||||
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'
|
||||
|
||||
// 数据库配置
|
||||
const DB_NAME = 'SmartisanNoteDB'
|
||||
const DB_VERSION = 2 // 更新版本号以确保数据库重新创建
|
||||
const NOTES_STORE = 'notes'
|
||||
const FOLDERS_STORE = 'folders'
|
||||
const SETTINGS_STORE = 'settings'
|
||||
|
||||
let db = null
|
||||
|
||||
/**
|
||||
* 打开数据库连接
|
||||
* @returns {Promise<IDBDatabase>} 数据库实例
|
||||
*/
|
||||
const openDB = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (db) {
|
||||
return resolve(db)
|
||||
}
|
||||
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION)
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('无法打开数据库'))
|
||||
}
|
||||
|
||||
request.onsuccess = () => {
|
||||
db = request.result
|
||||
resolve(db)
|
||||
}
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const database = event.target.result
|
||||
|
||||
// 删除现有的对象存储(如果版本已更改)
|
||||
if (event.oldVersion > 0) {
|
||||
if (database.objectStoreNames.contains(NOTES_STORE)) {
|
||||
database.deleteObjectStore(NOTES_STORE)
|
||||
}
|
||||
if (database.objectStoreNames.contains(FOLDERS_STORE)) {
|
||||
database.deleteObjectStore(FOLDERS_STORE)
|
||||
}
|
||||
if (database.objectStoreNames.contains(SETTINGS_STORE)) {
|
||||
database.deleteObjectStore(SETTINGS_STORE)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建便签存储对象
|
||||
const notesStore = database.createObjectStore(NOTES_STORE, { keyPath: 'id' })
|
||||
notesStore.createIndex('folderId', 'folderId', { unique: false })
|
||||
notesStore.createIndex('isStarred', 'isStarred', { unique: false })
|
||||
notesStore.createIndex('isDeleted', 'isDeleted', { unique: false })
|
||||
notesStore.createIndex('createdAt', 'createdAt', { unique: false })
|
||||
notesStore.createIndex('updatedAt', 'updatedAt', { unique: false })
|
||||
|
||||
// 创建文件夹存储对象
|
||||
database.createObjectStore(FOLDERS_STORE, { keyPath: 'id' })
|
||||
|
||||
// 创建设置存储对象
|
||||
database.createObjectStore(SETTINGS_STORE)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 从存储中获取数据
|
||||
* @param {string} storeName - 存储名称
|
||||
* @returns {Promise<Array>} 数据数组
|
||||
*/
|
||||
const getAllFromStore = async (storeName) => {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([storeName], 'readonly')
|
||||
const store = transaction.objectStore(storeName)
|
||||
const request = store.getAll()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result || [])
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`获取 ${storeName} 数据失败`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据到存储
|
||||
* @param {string} storeName - 存储名称
|
||||
* @param {Array} data - 要保存的数据数组
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const saveToStore = async (storeName, data) => {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([storeName], 'readwrite')
|
||||
const store = transaction.objectStore(storeName)
|
||||
|
||||
// 清除现有数据
|
||||
await new Promise((resolve, reject) => {
|
||||
const clearRequest = store.clear()
|
||||
clearRequest.onsuccess = () => resolve()
|
||||
clearRequest.onerror = () => reject(new Error(`清除 ${storeName} 数据失败`))
|
||||
})
|
||||
|
||||
// 添加新数据
|
||||
for (const item of data) {
|
||||
await new Promise((resolve, reject) => {
|
||||
const addRequest = store.add(item)
|
||||
addRequest.onsuccess = () => resolve()
|
||||
addRequest.onerror = () => reject(new Error(`保存 ${storeName} 数据失败`))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从存储中获取单个项
|
||||
* @param {string} storeName - 存储名称
|
||||
* @param {string} id - 项的ID
|
||||
* @returns {Promise<Object|null>} 项对象或null
|
||||
*/
|
||||
const getFromStore = async (storeName, id) => {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([storeName], 'readonly')
|
||||
const store = transaction.objectStore(storeName)
|
||||
const request = store.get(id)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result || null)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`获取 ${storeName} 项失败`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 向存储中添加项
|
||||
* @param {string} storeName - 存储名称
|
||||
* @param {Object} item - 要添加的项
|
||||
* @returns {Promise<Object>} 添加的项
|
||||
*/
|
||||
const addToStore = async (storeName, item) => {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([storeName], 'readwrite')
|
||||
const store = transaction.objectStore(storeName)
|
||||
const request = store.add(item)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(item)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`添加 ${storeName} 项失败`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新存储中的项
|
||||
* @param {string} storeName - 存储名称
|
||||
* @param {string} id - 项的ID
|
||||
* @param {Object} updates - 要更新的属性对象
|
||||
* @returns {Promise<Object|null>} 更新后的项或null
|
||||
*/
|
||||
const updateInStore = async (storeName, id, updates) => {
|
||||
const item = await getFromStore(storeName, id)
|
||||
if (!item) return null
|
||||
|
||||
const updatedItem = { ...item, ...updates }
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([storeName], 'readwrite')
|
||||
const store = transaction.objectStore(storeName)
|
||||
const request = store.put(updatedItem)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(updatedItem)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`更新 ${storeName} 项失败`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 从存储中删除项
|
||||
* @param {string} storeName - 存储名称
|
||||
* @param {string} id - 要删除的项的ID
|
||||
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
||||
*/
|
||||
const deleteFromStore = async (storeName, id) => {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([storeName], 'readwrite')
|
||||
const store = transaction.objectStore(storeName)
|
||||
const request = store.delete(id)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(true)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`删除 ${storeName} 项失败`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 便签操作函数
|
||||
// 提供便签的增删改查功能
|
||||
|
||||
/**
|
||||
* 获取所有便签数据
|
||||
* 从IndexedDB中读取便签数据
|
||||
* @returns {Promise<Array>} 便签数组
|
||||
*/
|
||||
export const getNotes = async () => {
|
||||
try {
|
||||
const notes = await getAllFromStore(NOTES_STORE)
|
||||
return ensureNotesDefaults(notes)
|
||||
} catch (error) {
|
||||
console.error('Error getting notes:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存便签数据
|
||||
* 将便签数组保存到IndexedDB
|
||||
* @param {Array} notes - 便签数组
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const saveNotes = async (notes) => {
|
||||
try {
|
||||
await saveToStore(NOTES_STORE, notes)
|
||||
} catch (error) {
|
||||
console.error('Error saving notes:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新便签
|
||||
* 创建一个新的便签对象并添加到便签列表中
|
||||
* @param {Object} note - 便签对象,包含便签内容和其他属性
|
||||
* @returns {Promise<Object>} 新创建的便签对象
|
||||
*/
|
||||
export const addNote = async (note) => {
|
||||
try {
|
||||
// 创建新的便签对象,添加必要的属性
|
||||
const newNote = {
|
||||
title: note.title || '',
|
||||
content: note.content || '',
|
||||
id: note.id || getTimestamp().toString(), // 使用时间戳生成唯一ID
|
||||
createdAt: note.createdAt || getCurrentDateTime(), // 创建时间
|
||||
updatedAt: note.updatedAt || getCurrentDateTime(), // 更新时间
|
||||
isStarred: note.isStarred || false, // 是否加星
|
||||
isTop: note.isTop || false, // 是否置顶
|
||||
hasImage: note.hasImage || false, // 是否包含图片
|
||||
isDeleted: note.isDeleted || false, // 是否已删除
|
||||
deletedAt: note.deletedAt || null, // 删除时间
|
||||
folderId: note.folderId || null, // 文件夹ID
|
||||
...note
|
||||
}
|
||||
|
||||
// 添加到存储
|
||||
await addToStore(NOTES_STORE, newNote)
|
||||
return newNote
|
||||
} catch (error) {
|
||||
console.error('Error adding note:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新便签
|
||||
* 根据ID查找并更新便签信息
|
||||
* @param {string} id - 便签ID
|
||||
* @param {Object} updates - 要更新的属性对象
|
||||
* @returns {Promise<Object|null>} 更新后的便签对象,如果未找到则返回null
|
||||
*/
|
||||
export const updateNote = async (id, updates) => {
|
||||
try {
|
||||
// 更新便签并保存
|
||||
const updatedNote = await updateInStore(NOTES_STORE, id, {
|
||||
...updates,
|
||||
updatedAt: getCurrentDateTime() // 更新最后修改时间
|
||||
})
|
||||
|
||||
return updatedNote
|
||||
} catch (error) {
|
||||
console.error('Error updating note:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除便签
|
||||
* 根据ID从便签列表中移除便签
|
||||
* @param {string} id - 要删除的便签ID
|
||||
* @returns {Promise<boolean>} 删除成功返回true,未找到便签返回false
|
||||
*/
|
||||
export const deleteNote = async (id) => {
|
||||
try {
|
||||
// 从存储中删除
|
||||
const result = await deleteFromStore(NOTES_STORE, id)
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Error deleting note:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 文件夹操作函数
|
||||
// 提供文件夹的增删改查功能
|
||||
|
||||
/**
|
||||
* 获取所有文件夹数据
|
||||
* 从IndexedDB中读取文件夹数据
|
||||
* @returns {Promise<Array>} 文件夹数组
|
||||
*/
|
||||
export const getFolders = async () => {
|
||||
try {
|
||||
const folders = await getAllFromStore(FOLDERS_STORE)
|
||||
return ensureFoldersDefaults(folders)
|
||||
} catch (error) {
|
||||
console.error('Error getting folders:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文件夹数据
|
||||
* 将文件夹数组保存到IndexedDB
|
||||
* @param {Array} folders - 文件夹数组
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const saveFolders = async (folders) => {
|
||||
try {
|
||||
await saveToStore(FOLDERS_STORE, folders)
|
||||
} catch (error) {
|
||||
console.error('Error saving folders:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新文件夹
|
||||
* 创建一个新的文件夹对象并添加到文件夹列表中
|
||||
* @param {Object} folder - 文件夹对象,包含文件夹名称等属性
|
||||
* @returns {Promise<Object>} 新创建的文件夹对象
|
||||
*/
|
||||
export const addFolder = async (folder) => {
|
||||
try {
|
||||
// 创建新的文件夹对象,添加必要的属性
|
||||
const newFolder = {
|
||||
name: folder.name || '',
|
||||
id: folder.id || getTimestamp().toString(), // 使用时间戳生成唯一ID
|
||||
createdAt: folder.createdAt || getCurrentDateTime(), // 创建时间
|
||||
...folder
|
||||
}
|
||||
|
||||
// 添加到存储
|
||||
await addToStore(FOLDERS_STORE, newFolder)
|
||||
return newFolder
|
||||
} catch (error) {
|
||||
console.error('Error adding folder:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 设置操作函数
|
||||
// 提供应用设置的读取和保存功能
|
||||
|
||||
/**
|
||||
* 获取应用设置
|
||||
* 从IndexedDB中读取设置数据
|
||||
* @returns {Promise<Object>} 设置对象,如果读取失败则返回默认设置
|
||||
*/
|
||||
export const getSettings = async () => {
|
||||
try {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([SETTINGS_STORE], 'readonly')
|
||||
const store = transaction.objectStore(SETTINGS_STORE)
|
||||
const request = store.get('settings')
|
||||
|
||||
const settings = await new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result || { cloudSync: false, darkMode: false })
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('获取设置失败'))
|
||||
}
|
||||
})
|
||||
|
||||
return settings
|
||||
} catch (error) {
|
||||
console.error('Error getting settings:', error)
|
||||
// 出错时返回默认设置
|
||||
return { cloudSync: false, darkMode: false }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存应用设置
|
||||
* 将设置对象保存到IndexedDB
|
||||
* @param {Object} settings - 设置对象
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const saveSettings = async (settings) => {
|
||||
try {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([SETTINGS_STORE], 'readwrite')
|
||||
const store = transaction.objectStore(SETTINGS_STORE)
|
||||
const request = store.put(settings, 'settings')
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => resolve()
|
||||
request.onerror = () => reject(new Error('保存设置失败'))
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error saving settings:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保数据有默认值
|
||||
* @param {Array} notes - 便签数组
|
||||
* @returns {Array} 处理后的便签数组
|
||||
*/
|
||||
const ensureNotesDefaults = (notes) => {
|
||||
return notes.map(note => ({
|
||||
title: note.title || '',
|
||||
content: note.content || '',
|
||||
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)
|
||||
}
|
||||
import { getCurrentDateTime, getTimestamp } from './dateUtils'
|
||||
|
||||
// 数据库配置
|
||||
const DB_NAME = 'SmartisanNoteDB'
|
||||
const DB_VERSION = 2 // 更新版本号以确保数据库重新创建
|
||||
const NOTES_STORE = 'notes'
|
||||
const FOLDERS_STORE = 'folders'
|
||||
const SETTINGS_STORE = 'settings'
|
||||
|
||||
let db = null
|
||||
|
||||
/**
|
||||
* 打开数据库连接
|
||||
* @returns {Promise<IDBDatabase>} 数据库实例
|
||||
*/
|
||||
const openDB = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (db) {
|
||||
return resolve(db)
|
||||
}
|
||||
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION)
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('无法打开数据库'))
|
||||
}
|
||||
|
||||
request.onsuccess = () => {
|
||||
db = request.result
|
||||
resolve(db)
|
||||
}
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const database = event.target.result
|
||||
|
||||
// 删除现有的对象存储(如果版本已更改)
|
||||
if (event.oldVersion > 0) {
|
||||
if (database.objectStoreNames.contains(NOTES_STORE)) {
|
||||
database.deleteObjectStore(NOTES_STORE)
|
||||
}
|
||||
if (database.objectStoreNames.contains(FOLDERS_STORE)) {
|
||||
database.deleteObjectStore(FOLDERS_STORE)
|
||||
}
|
||||
if (database.objectStoreNames.contains(SETTINGS_STORE)) {
|
||||
database.deleteObjectStore(SETTINGS_STORE)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建便签存储对象
|
||||
const notesStore = database.createObjectStore(NOTES_STORE, { keyPath: 'id' })
|
||||
notesStore.createIndex('folderId', 'folderId', { unique: false })
|
||||
notesStore.createIndex('isStarred', 'isStarred', { unique: false })
|
||||
notesStore.createIndex('isDeleted', 'isDeleted', { unique: false })
|
||||
notesStore.createIndex('createdAt', 'createdAt', { unique: false })
|
||||
notesStore.createIndex('updatedAt', 'updatedAt', { unique: false })
|
||||
|
||||
// 创建文件夹存储对象
|
||||
database.createObjectStore(FOLDERS_STORE, { keyPath: 'id' })
|
||||
|
||||
// 创建设置存储对象
|
||||
database.createObjectStore(SETTINGS_STORE)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 从存储中获取数据
|
||||
* @param {string} storeName - 存储名称
|
||||
* @returns {Promise<Array>} 数据数组
|
||||
*/
|
||||
const getAllFromStore = async (storeName) => {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([storeName], 'readonly')
|
||||
const store = transaction.objectStore(storeName)
|
||||
const request = store.getAll()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result || [])
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`获取 ${storeName} 数据失败`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据到存储
|
||||
* @param {string} storeName - 存储名称
|
||||
* @param {Array} data - 要保存的数据数组
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const saveToStore = async (storeName, data) => {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([storeName], 'readwrite')
|
||||
const store = transaction.objectStore(storeName)
|
||||
|
||||
// 清除现有数据
|
||||
await new Promise((resolve, reject) => {
|
||||
const clearRequest = store.clear()
|
||||
clearRequest.onsuccess = () => resolve()
|
||||
clearRequest.onerror = () => reject(new Error(`清除 ${storeName} 数据失败`))
|
||||
})
|
||||
|
||||
// 添加新数据
|
||||
for (const item of data) {
|
||||
await new Promise((resolve, reject) => {
|
||||
const addRequest = store.add(item)
|
||||
addRequest.onsuccess = () => resolve()
|
||||
addRequest.onerror = () => reject(new Error(`保存 ${storeName} 数据失败`))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从存储中获取单个项
|
||||
* @param {string} storeName - 存储名称
|
||||
* @param {string} id - 项的ID
|
||||
* @returns {Promise<Object|null>} 项对象或null
|
||||
*/
|
||||
const getFromStore = async (storeName, id) => {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([storeName], 'readonly')
|
||||
const store = transaction.objectStore(storeName)
|
||||
const request = store.get(id)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result || null)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`获取 ${storeName} 项失败`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 向存储中添加项
|
||||
* @param {string} storeName - 存储名称
|
||||
* @param {Object} item - 要添加的项
|
||||
* @returns {Promise<Object>} 添加的项
|
||||
*/
|
||||
const addToStore = async (storeName, item) => {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([storeName], 'readwrite')
|
||||
const store = transaction.objectStore(storeName)
|
||||
const request = store.add(item)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(item)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`添加 ${storeName} 项失败`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新存储中的项
|
||||
* @param {string} storeName - 存储名称
|
||||
* @param {string} id - 项的ID
|
||||
* @param {Object} updates - 要更新的属性对象
|
||||
* @returns {Promise<Object|null>} 更新后的项或null
|
||||
*/
|
||||
const updateInStore = async (storeName, id, updates) => {
|
||||
const item = await getFromStore(storeName, id)
|
||||
if (!item) return null
|
||||
|
||||
const updatedItem = { ...item, ...updates }
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([storeName], 'readwrite')
|
||||
const store = transaction.objectStore(storeName)
|
||||
const request = store.put(updatedItem)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(updatedItem)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`更新 ${storeName} 项失败`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 从存储中删除项
|
||||
* @param {string} storeName - 存储名称
|
||||
* @param {string} id - 要删除的项的ID
|
||||
* @returns {Promise<boolean>} 删除成功返回true,失败返回false
|
||||
*/
|
||||
const deleteFromStore = async (storeName, id) => {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([storeName], 'readwrite')
|
||||
const store = transaction.objectStore(storeName)
|
||||
const request = store.delete(id)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(true)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`删除 ${storeName} 项失败`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 便签操作函数
|
||||
// 提供便签的增删改查功能
|
||||
|
||||
/**
|
||||
* 获取所有便签数据
|
||||
* 从IndexedDB中读取便签数据
|
||||
* @returns {Promise<Array>} 便签数组
|
||||
*/
|
||||
export const getNotes = async () => {
|
||||
try {
|
||||
const notes = await getAllFromStore(NOTES_STORE)
|
||||
return ensureNotesDefaults(notes)
|
||||
} catch (error) {
|
||||
console.error('Error getting notes:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存便签数据
|
||||
* 将便签数组保存到IndexedDB
|
||||
* @param {Array} notes - 便签数组
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const saveNotes = async (notes) => {
|
||||
try {
|
||||
await saveToStore(NOTES_STORE, notes)
|
||||
} catch (error) {
|
||||
console.error('Error saving notes:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新便签
|
||||
* 创建一个新的便签对象并添加到便签列表中
|
||||
* @param {Object} note - 便签对象,包含便签内容和其他属性
|
||||
* @returns {Promise<Object>} 新创建的便签对象
|
||||
*/
|
||||
export const addNote = async (note) => {
|
||||
try {
|
||||
// 创建新的便签对象,添加必要的属性
|
||||
const newNote = {
|
||||
title: note.title || '',
|
||||
content: note.content || '',
|
||||
id: note.id || getTimestamp().toString(), // 使用时间戳生成唯一ID
|
||||
createdAt: note.createdAt || getCurrentDateTime(), // 创建时间
|
||||
updatedAt: note.updatedAt || getCurrentDateTime(), // 更新时间
|
||||
isStarred: note.isStarred || false, // 是否加星
|
||||
isTop: note.isTop || false, // 是否置顶
|
||||
hasImage: note.hasImage || false, // 是否包含图片
|
||||
isDeleted: note.isDeleted || false, // 是否已删除
|
||||
deletedAt: note.deletedAt || null, // 删除时间
|
||||
folderId: note.folderId || null, // 文件夹ID
|
||||
...note
|
||||
}
|
||||
|
||||
// 添加到存储
|
||||
await addToStore(NOTES_STORE, newNote)
|
||||
return newNote
|
||||
} catch (error) {
|
||||
console.error('Error adding note:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新便签
|
||||
* 根据ID查找并更新便签信息
|
||||
* @param {string} id - 便签ID
|
||||
* @param {Object} updates - 要更新的属性对象
|
||||
* @returns {Promise<Object|null>} 更新后的便签对象,如果未找到则返回null
|
||||
*/
|
||||
export const updateNote = async (id, updates) => {
|
||||
try {
|
||||
// 更新便签并保存
|
||||
const updatedNote = await updateInStore(NOTES_STORE, id, {
|
||||
...updates,
|
||||
updatedAt: getCurrentDateTime() // 更新最后修改时间
|
||||
})
|
||||
|
||||
return updatedNote
|
||||
} catch (error) {
|
||||
console.error('Error updating note:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除便签
|
||||
* 根据ID从便签列表中移除便签
|
||||
* @param {string} id - 要删除的便签ID
|
||||
* @returns {Promise<boolean>} 删除成功返回true,未找到便签返回false
|
||||
*/
|
||||
export const deleteNote = async (id) => {
|
||||
try {
|
||||
// 从存储中删除
|
||||
const result = await deleteFromStore(NOTES_STORE, id)
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Error deleting note:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 文件夹操作函数
|
||||
// 提供文件夹的增删改查功能
|
||||
|
||||
/**
|
||||
* 获取所有文件夹数据
|
||||
* 从IndexedDB中读取文件夹数据
|
||||
* @returns {Promise<Array>} 文件夹数组
|
||||
*/
|
||||
export const getFolders = async () => {
|
||||
try {
|
||||
const folders = await getAllFromStore(FOLDERS_STORE)
|
||||
return ensureFoldersDefaults(folders)
|
||||
} catch (error) {
|
||||
console.error('Error getting folders:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文件夹数据
|
||||
* 将文件夹数组保存到IndexedDB
|
||||
* @param {Array} folders - 文件夹数组
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const saveFolders = async (folders) => {
|
||||
try {
|
||||
await saveToStore(FOLDERS_STORE, folders)
|
||||
} catch (error) {
|
||||
console.error('Error saving folders:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新文件夹
|
||||
* 创建一个新的文件夹对象并添加到文件夹列表中
|
||||
* @param {Object} folder - 文件夹对象,包含文件夹名称等属性
|
||||
* @returns {Promise<Object>} 新创建的文件夹对象
|
||||
*/
|
||||
export const addFolder = async (folder) => {
|
||||
try {
|
||||
// 创建新的文件夹对象,添加必要的属性
|
||||
const newFolder = {
|
||||
name: folder.name || '',
|
||||
id: folder.id || getTimestamp().toString(), // 使用时间戳生成唯一ID
|
||||
createdAt: folder.createdAt || getCurrentDateTime(), // 创建时间
|
||||
...folder
|
||||
}
|
||||
|
||||
// 添加到存储
|
||||
await addToStore(FOLDERS_STORE, newFolder)
|
||||
return newFolder
|
||||
} catch (error) {
|
||||
console.error('Error adding folder:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新文件夹
|
||||
* 根据ID查找并更新文件夹信息
|
||||
* @param {string} id - 文件夹ID
|
||||
* @param {Object} updates - 要更新的属性对象
|
||||
* @returns {Promise<Object|null>} 更新后的文件夹对象,如果未找到则返回null
|
||||
*/
|
||||
export const updateFolder = async (id, updates) => {
|
||||
try {
|
||||
// 更新文件夹并保存
|
||||
const updatedFolder = await updateInStore(FOLDERS_STORE, id, updates)
|
||||
return updatedFolder
|
||||
} catch (error) {
|
||||
console.error('Error updating folder:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件夹
|
||||
* 根据ID从文件夹列表中移除文件夹
|
||||
* @param {string} id - 要删除的文件夹ID
|
||||
* @returns {Promise<boolean>} 删除成功返回true,未找到文件夹返回false
|
||||
*/
|
||||
export const deleteFolder = async (id) => {
|
||||
try {
|
||||
// 从存储中删除
|
||||
const result = await deleteFromStore(FOLDERS_STORE, id)
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Error deleting folder:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 设置操作函数
|
||||
// 提供应用设置的读取和保存功能
|
||||
|
||||
/**
|
||||
* 获取应用设置
|
||||
* 从IndexedDB中读取设置数据
|
||||
* @returns {Promise<Object>} 设置对象,如果读取失败则返回默认设置
|
||||
*/
|
||||
export const getSettings = async () => {
|
||||
try {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([SETTINGS_STORE], 'readonly')
|
||||
const store = transaction.objectStore(SETTINGS_STORE)
|
||||
const request = store.get('settings')
|
||||
|
||||
const settings = await new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result || { cloudSync: false, darkMode: false })
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('获取设置失败'))
|
||||
}
|
||||
})
|
||||
|
||||
return settings
|
||||
} catch (error) {
|
||||
console.error('Error getting settings:', error)
|
||||
// 出错时返回默认设置
|
||||
return { cloudSync: false, darkMode: false }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存应用设置
|
||||
* 将设置对象保存到IndexedDB
|
||||
* @param {Object} settings - 设置对象
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const saveSettings = async (settings) => {
|
||||
try {
|
||||
const database = await openDB()
|
||||
const transaction = database.transaction([SETTINGS_STORE], 'readwrite')
|
||||
const store = transaction.objectStore(SETTINGS_STORE)
|
||||
const request = store.put(settings, 'settings')
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => resolve()
|
||||
request.onerror = () => reject(new Error('保存设置失败'))
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error saving settings:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保数据有默认值
|
||||
* @param {Array} notes - 便签数组
|
||||
* @returns {Array} 处理后的便签数组
|
||||
*/
|
||||
const ensureNotesDefaults = (notes) => {
|
||||
return notes.map(note => ({
|
||||
title: note.title || '',
|
||||
content: note.content || '',
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user