You've already forked SmartisanNote.Remake
开始完善便签新建、编辑逻辑
This commit is contained in:
@@ -1,29 +1,17 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<Header
|
||||
title="文件夹"
|
||||
:onBack="() => window.history.back()"
|
||||
/>
|
||||
<div style="padding: 10px; background-color: var(--background)">
|
||||
<div style="display: flex; align-items: center; background-color: var(--search-bar-background); border-radius: 8px; padding: 0 10px">
|
||||
<ion-icon :icon="search" style="font-size: 20px; color: var(--text-tertiary)"></ion-icon>
|
||||
<ion-input
|
||||
placeholder="搜索文件夹..."
|
||||
:value="searchQuery"
|
||||
@ionChange="e => setSearchQuery(e.detail.value)"
|
||||
style="--padding-start: 10px; --padding-end: 10px; flex: 1; font-size: 16px; color: var(--text-primary)"
|
||||
></ion-input>
|
||||
<ion-button
|
||||
v-if="searchQuery.length > 0"
|
||||
fill="clear"
|
||||
@click="() => setSearchQuery('')"
|
||||
>
|
||||
<ion-icon :icon="closeCircle" style="font-size: 20px; color: var(--text-tertiary)"></ion-icon>
|
||||
<Header title="文件夹" :onBack="() => window.history.back()" />
|
||||
<div class="folder-page-container">
|
||||
<div class="search-container">
|
||||
<ion-icon :icon="search" class="search-icon"></ion-icon>
|
||||
<ion-input placeholder="搜索文件夹..." :value="searchQuery" @ionChange="e => setSearchQuery(e.detail.value)" class="search-input"></ion-input>
|
||||
<ion-button v-if="searchQuery.length > 0" fill="clear" @click="() => setSearchQuery('')">
|
||||
<ion-icon :icon="closeCircle" class="clear-icon"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
<ion-content>
|
||||
<ion-list style="background-color: var(--background); padding: 0 16px; --ion-item-background: var(--background)">
|
||||
<ion-list class="folder-list">
|
||||
<FolderManage
|
||||
:allCount="allNotesCount"
|
||||
:starredCount="starredNotesCount"
|
||||
@@ -33,47 +21,46 @@
|
||||
:onAllClick="() => handleFolderPress('all')"
|
||||
:onStarredClick="() => handleFolderPress('starred')"
|
||||
:onTrashClick="() => handleFolderPress('trash')"
|
||||
:onArchiveClick="() => handleFolderPress('archive')"
|
||||
/>
|
||||
:onArchiveClick="() => handleFolderPress('archive')" />
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useAppStore } from '../stores/useAppStore';
|
||||
import { search, closeCircle } from 'ionicons/icons';
|
||||
import FolderManage from '../components/FolderManage.vue';
|
||||
import Header from '../components/Header.vue';
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useAppStore } from '../stores/useAppStore'
|
||||
import { search, closeCircle } from 'ionicons/icons'
|
||||
import FolderManage from '../components/FolderManage.vue'
|
||||
import Header from '../components/Header.vue'
|
||||
|
||||
const store = useAppStore();
|
||||
const store = useAppStore()
|
||||
|
||||
// 加载初始数据
|
||||
onMounted(() => {
|
||||
store.loadData();
|
||||
});
|
||||
store.loadData()
|
||||
})
|
||||
|
||||
const searchQuery = ref('');
|
||||
const selectedFolder = ref('all');
|
||||
const searchQuery = ref('')
|
||||
const selectedFolder = ref('all')
|
||||
|
||||
// Calculate note count for each folder
|
||||
const foldersWithCount = computed(() => {
|
||||
return store.folders.map(folder => {
|
||||
const noteCount = store.notes.filter(note => note.folderId === folder.id).length;
|
||||
const noteCount = store.notes.filter(note => note.folderId === folder.id).length
|
||||
return {
|
||||
...folder,
|
||||
noteCount,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Add default folders at the beginning
|
||||
const allNotesCount = computed(() => store.notes.length);
|
||||
const starredNotesCount = computed(() => store.notes.filter(note => note.isStarred).length);
|
||||
const allNotesCount = computed(() => store.notes.length)
|
||||
const starredNotesCount = computed(() => store.notes.filter(note => note.isStarred).length)
|
||||
// Assuming we have a way to track deleted notes in the future
|
||||
const trashNotesCount = 0;
|
||||
const archiveCount = 0;
|
||||
const trashNotesCount = 0
|
||||
const archiveCount = 0
|
||||
|
||||
const foldersWithAllNotes = computed(() => {
|
||||
return [
|
||||
@@ -81,39 +68,76 @@ const foldersWithAllNotes = computed(() => {
|
||||
{ id: 'starred', name: '加星便签', noteCount: starredNotesCount.value, createdAt: new Date() },
|
||||
{ id: 'trash', name: '回收站', noteCount: trashNotesCount, createdAt: new Date() },
|
||||
...foldersWithCount.value,
|
||||
];
|
||||
});
|
||||
]
|
||||
})
|
||||
|
||||
const handleFolderPress = (folderId) => {
|
||||
const handleFolderPress = folderId => {
|
||||
// 更新选中的文件夹状态
|
||||
selectedFolder.value = folderId;
|
||||
selectedFolder.value = folderId
|
||||
// 在实际应用中,这里会将选中的文件夹传递回NoteListScreen
|
||||
// 通过导航参数传递选中的文件夹ID
|
||||
window.location.hash = `#/notes?folder=${folderId}`;
|
||||
};
|
||||
window.location.hash = `#/notes?folder=${folderId}`
|
||||
}
|
||||
|
||||
const handleAddFolder = () => {
|
||||
// In a full implementation, this would open a folder creation dialog
|
||||
console.log('Add folder pressed');
|
||||
};
|
||||
console.log('Add folder pressed')
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
// In a full implementation, this would filter folders based on searchQuery
|
||||
console.log('Search for:', searchQuery.value);
|
||||
};
|
||||
console.log('Search for:', searchQuery.value)
|
||||
}
|
||||
|
||||
const handleBackPress = () => {
|
||||
window.history.back();
|
||||
};
|
||||
window.history.back()
|
||||
}
|
||||
|
||||
// Filter folders based on search query
|
||||
const filteredFolders = computed(() => {
|
||||
return foldersWithAllNotes.value.filter(folder =>
|
||||
folder.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
);
|
||||
});
|
||||
return foldersWithAllNotes.value.filter(folder => folder.name.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
||||
})
|
||||
|
||||
const setSearchQuery = (value) => {
|
||||
searchQuery.value = value;
|
||||
};
|
||||
</script>
|
||||
const setSearchQuery = value => {
|
||||
searchQuery.value = value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.folder-page-container {
|
||||
padding: 10px;
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--search-bar-background);
|
||||
border-radius: 8px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 20px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
--padding-start: 10px;
|
||||
--padding-end: 10px;
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
font-size: 20px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.folder-list {
|
||||
background-color: var(--background);
|
||||
padding: 0 16px;
|
||||
--ion-item-background: var(--background);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Header :onBack="handleCancel" :onAction="handleAction" actionIcon="save" />
|
||||
|
||||
<!-- 顶部信息栏 -->
|
||||
<div class="header-info" v-if="isEditing">
|
||||
<div class="header-info">
|
||||
<span class="edit-time">{{ formattedTime }}</span>
|
||||
<span>|</span>
|
||||
<span class="word-count">{{ wordCount }}</span>
|
||||
@@ -12,7 +12,11 @@
|
||||
|
||||
<!-- 富文本编辑器 -->
|
||||
<div class="editor-container">
|
||||
<RichTextEditor ref="editorRef" v-model="content" class="rich-text-editor" />
|
||||
<RichTextEditor
|
||||
ref="editorRef"
|
||||
:modelValue="content"
|
||||
@update:modelValue="handleContentChange"
|
||||
class="rich-text-editor" />
|
||||
</div>
|
||||
|
||||
<ion-alert
|
||||
@@ -35,34 +39,123 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
||||
import { useAppStore } from '../stores/useAppStore'
|
||||
import Header from '../components/Header.vue'
|
||||
import RichTextEditor from '../components/RichTextEditor.vue'
|
||||
|
||||
const props = defineProps({
|
||||
noteId: {
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
// 为了保持向后兼容性,我们也支持noteId属性
|
||||
const noteId = computed(() => props.id || props.noteId)
|
||||
|
||||
const store = useAppStore()
|
||||
const editorRef = ref(null)
|
||||
|
||||
// 设置便签内容的函数
|
||||
const setNoteContent = async (noteId) => {
|
||||
// 确保store数据已加载
|
||||
if (store.notes.length === 0) {
|
||||
await store.loadData()
|
||||
console.log('Store loaded, notes count:', store.notes.length)
|
||||
}
|
||||
|
||||
const note = store.notes.find(n => n.id === noteId)
|
||||
console.log('Found note:', note)
|
||||
|
||||
// 确保编辑器已经初始化
|
||||
await nextTick()
|
||||
console.log('Editor ref:', editorRef.value)
|
||||
|
||||
if (note) {
|
||||
console.log('Setting content:', note.content)
|
||||
// 无论editorRef是否可用,都先设置content的值
|
||||
content.value = note.content || ''
|
||||
// 如果editorRef可用,直接设置内容
|
||||
if (editorRef.value) {
|
||||
editorRef.value.setContent(note.content || '')
|
||||
}
|
||||
} else {
|
||||
console.log('Note not available')
|
||||
}
|
||||
}
|
||||
|
||||
// 加载初始数据
|
||||
onMounted(async () => {
|
||||
console.log('NoteEditorPage mounted')
|
||||
await store.loadData()
|
||||
console.log('Store loaded, notes count:', store.notes.length)
|
||||
|
||||
// 如果是编辑现有便签,在组件挂载后设置内容
|
||||
if (noteId.value) {
|
||||
await setNoteContent(noteId.value)
|
||||
}
|
||||
})
|
||||
|
||||
// 监听noteId变化,确保在编辑器准备好后设置内容
|
||||
watch(noteId, async (newNoteId) => {
|
||||
console.log('Note ID changed:', newNoteId)
|
||||
if (newNoteId) {
|
||||
await setNoteContent(newNoteId)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 监听store变化,确保在store加载后设置内容
|
||||
watch(() => store.notes, async (newNotes) => {
|
||||
if (noteId.value && newNotes.length > 0) {
|
||||
await setNoteContent(noteId.value)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// Check if we're editing an existing note
|
||||
const isEditing = !!props.noteId
|
||||
const existingNote = isEditing ? store.notes.find(n => n.id === props.noteId) : null
|
||||
const isEditing = !!noteId.value
|
||||
const existingNote = isEditing ? store.notes.find(n => n.id === noteId.value) : null
|
||||
|
||||
// Initialize state with existing note data or empty strings
|
||||
const content = ref(existingNote?.content || '')
|
||||
|
||||
// 当组件挂载时,确保编辑器初始化为空内容(针对新建便签)
|
||||
onMounted(() => {
|
||||
if (!isEditing && editorRef.value) {
|
||||
console.log('Initializing editor for new note')
|
||||
editorRef.value.setContent('')
|
||||
}
|
||||
})
|
||||
|
||||
// 监听store变化,确保在store加载后设置内容
|
||||
watch(() => store.notes, async (newNotes) => {
|
||||
if (noteId.value && newNotes.length > 0) {
|
||||
await setNoteContent(noteId.value)
|
||||
}
|
||||
}, { immediate: true })
|
||||
const showAlert = ref(false)
|
||||
|
||||
// 防抖函数
|
||||
const debounce = (func, delay) => {
|
||||
let timeoutId
|
||||
return function (...args) {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(() => func.apply(this, args), delay)
|
||||
}
|
||||
}
|
||||
|
||||
// 防抖处理内容变化
|
||||
const debouncedHandleContentChange = debounce((newContent) => {
|
||||
content.value = newContent
|
||||
console.log('Content updated:', newContent)
|
||||
}, 300)
|
||||
|
||||
// 监听编辑器内容变化
|
||||
const handleContentChange = (newContent) => {
|
||||
console.log('Editor content changed:', newContent)
|
||||
debouncedHandleContentChange(newContent)
|
||||
}
|
||||
|
||||
// 计算属性
|
||||
const formattedTime = computed(() => {
|
||||
const now = new Date()
|
||||
@@ -93,15 +186,18 @@ const wordCount = computed(() => {
|
||||
// 处理保存
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
// 获取编辑器中的实际内容
|
||||
const editorContent = editorRef.value ? editorRef.value.getContent() : content.value
|
||||
|
||||
if (isEditing && existingNote) {
|
||||
// Update existing note
|
||||
await store.updateNote(props.noteId, {
|
||||
content: content.value,
|
||||
await store.updateNote(noteId.value, {
|
||||
content: editorContent,
|
||||
})
|
||||
} else {
|
||||
// Create new note
|
||||
await store.addNote({
|
||||
content: content.value,
|
||||
content: editorContent,
|
||||
isStarred: false,
|
||||
})
|
||||
}
|
||||
@@ -129,9 +225,12 @@ const handleCancel = () => {
|
||||
// 处理创建(用于新建便签)
|
||||
const handleCreate = async () => {
|
||||
try {
|
||||
// 获取编辑器中的实际内容
|
||||
const editorContent = editorRef.value ? editorRef.value.getContent() : content.value
|
||||
|
||||
// Create new note
|
||||
await store.addNote({
|
||||
content: content.value,
|
||||
content: editorContent,
|
||||
isStarred: false,
|
||||
})
|
||||
|
||||
@@ -185,7 +284,7 @@ const setShowAlert = value => {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
padding: 8px 16px;
|
||||
padding: 1.5rem 16px 0.7rem 16px;
|
||||
background-color: var(--background-card);
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: 0.7rem;
|
||||
|
||||
@@ -6,41 +6,26 @@
|
||||
<!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
|
||||
<div
|
||||
v-if="isFolderExpanded"
|
||||
style="position: absolute; top: 50px; left: 10%; right: 10%; z-index: 1000; background-color: var(--background-card); border-radius: 8px; box-shadow: 0 2px 4px var(--shadow); border: 1px solid #f0ece7; overflow: hidden">
|
||||
class="folder-list">
|
||||
<FolderManage
|
||||
:allCount="notes.length"
|
||||
:allCount="allNotesCount"
|
||||
:starredCount="starredNotesCount"
|
||||
:trashCount="0"
|
||||
:trashCount="trashNotesCount"
|
||||
:archiveCount="0"
|
||||
:selectedFolder="currentFolder"
|
||||
:onAllClick="
|
||||
() => {
|
||||
setCurrentFolder('all')
|
||||
setIsFolderExpanded(false)
|
||||
}
|
||||
"
|
||||
:onStarredClick="
|
||||
() => {
|
||||
setCurrentFolder('starred')
|
||||
setIsFolderExpanded(false)
|
||||
}
|
||||
"
|
||||
:onTrashClick="
|
||||
() => {
|
||||
setCurrentFolder('trash')
|
||||
setIsFolderExpanded(false)
|
||||
}
|
||||
" />
|
||||
:onAllClick="handleAllNotesClick"
|
||||
:onStarredClick="handleStarredNotesClick"
|
||||
:onTrashClick="handleTrashNotesClick" />
|
||||
</div>
|
||||
<!-- 点击外部区域收起文件夹列表的覆盖层 -->
|
||||
<div v-if="isFolderExpanded" @click="() => setIsFolderExpanded(false)" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: transparent; z-index: 99"></div>
|
||||
<div v-if="isFolderExpanded" @click="() => setIsFolderExpanded(false)" class="folder-overlay"></div>
|
||||
|
||||
<div style="padding: 0.8rem 0.5rem">
|
||||
<div class="search-container">
|
||||
<SearchBar v-model="searchQuery" @search="handleSearch" @clear="handleClearSearch" @focus="handleSearchFocus" @blur="handleSearchBlur" />
|
||||
</div>
|
||||
|
||||
<div style="flex: 1">
|
||||
<div v-for="note in filteredAndSortedNotes" :key="note.id" style="margin: 0.4rem 0">
|
||||
<div class="notes-container">
|
||||
<div v-for="note in filteredAndSortedNotes" :key="note.id" class="note-item">
|
||||
<NoteItem
|
||||
:title="note.title"
|
||||
:content="note.content"
|
||||
@@ -76,7 +61,6 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useAppStore } from '../stores/useAppStore'
|
||||
import { create, settings } from 'ionicons/icons'
|
||||
import NoteItem from '../components/NoteItem.vue'
|
||||
import Header from '../components/Header.vue'
|
||||
import FolderManage from '../components/FolderManage.vue'
|
||||
@@ -102,50 +86,67 @@ const currentFolder = ref('all') // 默认文件夹是"全部便签"
|
||||
const showAlert = ref(false)
|
||||
const noteToDelete = ref(null)
|
||||
|
||||
// 计算加星便签数量
|
||||
// 计算加星便签数量(未删除的)
|
||||
const starredNotesCount = computed(() => {
|
||||
return store.notes.filter(note => note.isStarred).length
|
||||
return store.notes.filter(note => note.isStarred && !note.isDeleted).length
|
||||
})
|
||||
|
||||
// 计算置顶便签数量
|
||||
const topNotesCount = computed(() => {
|
||||
return filteredAndSortedNotes.value.filter(note => note.isTop).length
|
||||
// 计算回收站便签数量
|
||||
const trashNotesCount = computed(() => {
|
||||
return store.notes.filter(note => note.isDeleted).length
|
||||
})
|
||||
|
||||
// 根据当前文件夹过滤便签
|
||||
const filteredNotes = computed(() => {
|
||||
// 预处理搜索查询,提高性能
|
||||
const lowerCaseQuery = searchQuery.value.toLowerCase().trim()
|
||||
|
||||
return store.notes.filter(note => {
|
||||
// 先检查搜索条件
|
||||
const matchesSearch = !lowerCaseQuery ||
|
||||
note.title.toLowerCase().includes(lowerCaseQuery) ||
|
||||
note.content.toLowerCase().includes(lowerCaseQuery)
|
||||
|
||||
if (!matchesSearch) return false
|
||||
|
||||
// 再检查文件夹条件
|
||||
switch (currentFolder.value) {
|
||||
case 'all':
|
||||
return true
|
||||
// 全部便签中不显示已删除的便签
|
||||
return !note.isDeleted
|
||||
case 'starred':
|
||||
return note.isStarred
|
||||
// 加星便签中只显示未删除的加星便签
|
||||
return note.isStarred && !note.isDeleted
|
||||
case 'trash':
|
||||
// 假设我们有一个isDeleted属性来标识已删除的便签
|
||||
return note.isDeleted || false
|
||||
// 回收站中只显示已删除的便签
|
||||
return note.isDeleted
|
||||
default:
|
||||
return note.folderId === currentFolder.value
|
||||
// 自定义文件夹中不显示已删除的便签
|
||||
return note.folderId === currentFolder.value && !note.isDeleted
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Filter and sort notes
|
||||
const filteredAndSortedNotes = computed(() => {
|
||||
return filteredNotes.value
|
||||
.filter(note => note.title.toLowerCase().includes(searchQuery.value.toLowerCase()) || note.content.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
||||
.sort((a, b) => {
|
||||
// 置顶的便签排在前面
|
||||
if (a.isTop && !b.isTop) return -1
|
||||
if (!a.isTop && b.isTop) return 1
|
||||
return [...filteredNotes.value].sort((a, b) => {
|
||||
// 置顶的便签排在前面
|
||||
if (a.isTop && !b.isTop) return -1
|
||||
if (!a.isTop && b.isTop) return 1
|
||||
|
||||
if (sortBy.value === 'title') {
|
||||
// 根据排序方式排序
|
||||
switch (sortBy.value) {
|
||||
case 'title':
|
||||
return a.title.localeCompare(b.title)
|
||||
} else if (sortBy.value === 'starred') {
|
||||
case 'starred':
|
||||
// 加星的便签排在前面
|
||||
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0)
|
||||
} else {
|
||||
case 'date':
|
||||
default:
|
||||
// 按更新时间倒序排列(最新的在前)
|
||||
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 计算头部标题
|
||||
@@ -158,10 +159,15 @@ const headerTitle = computed(() => {
|
||||
case 'trash':
|
||||
return '回收站'
|
||||
default:
|
||||
return '文件夹'
|
||||
return '全部便签'
|
||||
}
|
||||
})
|
||||
|
||||
// 计算全部便签数量(未删除的)
|
||||
const allNotesCount = computed(() => {
|
||||
return store.notes.filter(note => !note.isDeleted).length
|
||||
})
|
||||
|
||||
const handleNotePress = noteId => {
|
||||
// 导航到编辑页面的逻辑将在路由中处理
|
||||
window.location.hash = `#/editor/${noteId}`
|
||||
@@ -187,21 +193,44 @@ const handleDeleteNote = noteId => {
|
||||
const handleStarToggle = async noteId => {
|
||||
const note = store.notes.find(n => n.id === noteId)
|
||||
if (note) {
|
||||
await store.updateNote(noteId, { isStarred: !note.isStarred })
|
||||
try {
|
||||
await store.updateNote(noteId, { isStarred: !note.isStarred })
|
||||
console.log(`Note ${noteId} starred status updated`)
|
||||
} catch (error) {
|
||||
console.error('Failed to update note star status:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleTopToggle = async noteId => {
|
||||
const note = store.notes.find(n => n.id === noteId)
|
||||
if (note) {
|
||||
await store.updateNote(noteId, { isTop: !note.isTop })
|
||||
try {
|
||||
await store.updateNote(noteId, { isTop: !note.isTop })
|
||||
console.log(`Note ${noteId} top status updated`)
|
||||
} catch (error) {
|
||||
console.error('Failed to update note top status:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const confirmDeleteNote = () => {
|
||||
const confirmDeleteNote = async () => {
|
||||
if (noteToDelete.value) {
|
||||
store.deleteNote(noteToDelete.value)
|
||||
noteToDelete.value = null
|
||||
try {
|
||||
// 检查当前是否在回收站中
|
||||
if (currentFolder.value === 'trash') {
|
||||
// 在回收站中删除便签,彻底删除
|
||||
await store.permanentlyDeleteNote(noteToDelete.value)
|
||||
console.log(`Note ${noteToDelete.value} permanently deleted`)
|
||||
} else {
|
||||
// 不在回收站中,将便签移至回收站
|
||||
await store.moveToTrash(noteToDelete.value)
|
||||
console.log(`Note ${noteToDelete.value} moved to trash`)
|
||||
}
|
||||
noteToDelete.value = null
|
||||
} catch (error) {
|
||||
console.error('Failed to delete note:', error)
|
||||
}
|
||||
}
|
||||
showAlert.value = false
|
||||
}
|
||||
@@ -215,6 +244,21 @@ const handleSort = () => {
|
||||
console.log('Sort by:', sortOptions[nextIndex])
|
||||
}
|
||||
|
||||
const handleAllNotesClick = () => {
|
||||
setCurrentFolder('all')
|
||||
setIsFolderExpanded(false)
|
||||
}
|
||||
|
||||
const handleStarredNotesClick = () => {
|
||||
setCurrentFolder('starred')
|
||||
setIsFolderExpanded(false)
|
||||
}
|
||||
|
||||
const handleTrashNotesClick = () => {
|
||||
setCurrentFolder('trash')
|
||||
setIsFolderExpanded(false)
|
||||
}
|
||||
|
||||
const handleFolderPress = () => {
|
||||
// 导航到文件夹页面的逻辑将在路由中处理
|
||||
window.location.hash = '#/folders'
|
||||
@@ -228,29 +272,77 @@ const handleSettingsPress = () => {
|
||||
const handleFolderToggle = () => {
|
||||
// 在实际应用中,这里会触发文件夹列表的展开/收起
|
||||
isFolderExpanded.value = !isFolderExpanded.value
|
||||
console.log('Folder expanded:', !isFolderExpanded.value)
|
||||
}
|
||||
|
||||
const handleSearch = query => {
|
||||
// 搜索功能已在computed属性filteredAndSortedNotes中实现
|
||||
console.log('Search for:', query)
|
||||
|
||||
// 可以在这里添加搜索统计或其它功能
|
||||
if (query && query.length > 0) {
|
||||
console.log(`Found ${filteredAndSortedNotes.value.length} matching notes`)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClearSearch = () => {
|
||||
// 清除搜索已在v-model中处理
|
||||
console.log('Search cleared')
|
||||
|
||||
// 清除搜索后可以重置一些状态
|
||||
setSearchQuery('')
|
||||
}
|
||||
|
||||
const handleSearchFocus = () => {
|
||||
console.log('Search bar focused')
|
||||
// 可以在这里添加获得焦点时的特殊处理
|
||||
}
|
||||
|
||||
const handleSearchBlur = () => {
|
||||
console.log('Search bar blurred')
|
||||
// 可以在这里添加失去焦点时的特殊处理
|
||||
}
|
||||
|
||||
// 防抖搜索函数,避免频繁触发搜索
|
||||
const debounceSearch = (func, delay) => {
|
||||
let timeoutId
|
||||
return function (...args) {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(() => func.apply(this, args), delay)
|
||||
}
|
||||
}
|
||||
|
||||
// 防抖搜索处理
|
||||
const debouncedHandleSearch = debounceSearch((query) => {
|
||||
handleSearch(query)
|
||||
}, 300)
|
||||
|
||||
// 改进的日期格式化函数
|
||||
const formatDate = dateString => {
|
||||
return new Date(dateString).toLocaleDateString()
|
||||
const date = new Date(dateString)
|
||||
const now = new Date()
|
||||
|
||||
// 计算日期差
|
||||
const diffTime = now - date
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
|
||||
|
||||
// 今天的便签显示时间
|
||||
if (diffDays === 0) {
|
||||
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// 昨天的便签显示"昨天"
|
||||
if (diffDays === 1) {
|
||||
return '昨天'
|
||||
}
|
||||
|
||||
// 一周内的便签显示星期几
|
||||
if (diffDays < 7) {
|
||||
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
return weekdays[date.getDay()]
|
||||
}
|
||||
|
||||
// 超过一周的便签显示月日
|
||||
return `${date.getMonth() + 1}/${date.getDate()}`
|
||||
}
|
||||
|
||||
const setCurrentFolder = folder => {
|
||||
@@ -278,4 +370,39 @@ const notes = computed(() => store.notes)
|
||||
background: url(/assets/icons/drawable-xxhdpi/note_background.png);
|
||||
background-size: cover;
|
||||
}
|
||||
</style>
|
||||
|
||||
.folder-list {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 10%;
|
||||
right: 10%;
|
||||
z-index: 1000;
|
||||
background-color: var(--background-card);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px var(--shadow);
|
||||
border: 1px solid #f0ece7;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.folder-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: transparent;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
padding: 0.8rem 0.5rem;
|
||||
}
|
||||
|
||||
.notes-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.note-item {
|
||||
margin: 0.4rem 0;
|
||||
}
|
||||
</style>
|
||||
@@ -5,31 +5,31 @@
|
||||
:onBack="handleBackPress"
|
||||
/>
|
||||
|
||||
<ion-content style="background-color: var(--background)">
|
||||
<div style="margin-bottom: 12px; background-color: var(--background-card)">
|
||||
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
|
||||
<ion-content class="settings-content">
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
账户
|
||||
</div>
|
||||
<div button @click="handleLogin" style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
|
||||
<div style="font-size: 16px; color: var(--text-primary)">登录云同步</div>
|
||||
<div style="font-size: 15px; color: var(--text-tertiary)">未登录</div>
|
||||
<div button @click="handleLogin" class="settings-item settings-item-clickable">
|
||||
<div class="item-text-primary">登录云同步</div>
|
||||
<div class="item-text-tertiary">未登录</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 12px; background-color: var(--background-card)">
|
||||
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
偏好设置
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border)">
|
||||
<div style="font-size: 16px; color: var(--text-primary)">云同步</div>
|
||||
<div class="settings-item settings-item-border">
|
||||
<div class="item-text-primary">云同步</div>
|
||||
<ion-toggle
|
||||
slot="end"
|
||||
:checked="settings.cloudSync"
|
||||
@ion-change="toggleCloudSync"
|
||||
></ion-toggle>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px">
|
||||
<div style="font-size: 16px; color: var(--text-primary)">深色模式</div>
|
||||
<div class="settings-item">
|
||||
<div class="item-text-primary">深色模式</div>
|
||||
<ion-toggle
|
||||
slot="end"
|
||||
:checked="settings.darkMode"
|
||||
@@ -38,41 +38,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 12px; background-color: var(--background-card)">
|
||||
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
数据管理
|
||||
</div>
|
||||
<div button @click="handleBackup" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_save_pic.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
|
||||
<div style="font-size: 16px; color: var(--text-primary)">备份便签</div>
|
||||
<div button @click="handleBackup" class="settings-item settings-item-clickable settings-item-border">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_save_pic.png'" class="item-icon" />
|
||||
<div class="item-text-primary">备份便签</div>
|
||||
</div>
|
||||
<div button @click="handleRestore" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_restore.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
|
||||
<div style="font-size: 16px; color: var(--text-primary)">恢复便签</div>
|
||||
<div button @click="handleRestore" class="settings-item settings-item-clickable settings-item-border">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_restore.png'" class="item-icon" />
|
||||
<div class="item-text-primary">恢复便签</div>
|
||||
</div>
|
||||
<div button @click="handleExport" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_share.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
|
||||
<div style="font-size: 16px; color: var(--text-primary)">导出便签</div>
|
||||
<div button @click="handleExport" class="settings-item settings-item-clickable settings-item-border">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_share.png'" class="item-icon" />
|
||||
<div class="item-text-primary">导出便签</div>
|
||||
</div>
|
||||
<div button @click="handleImport" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; cursor: pointer">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_load_error.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
|
||||
<div style="font-size: 16px; color: var(--text-primary)">导入便签</div>
|
||||
<div button @click="handleImport" class="settings-item settings-item-clickable">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_load_error.png'" class="item-icon" />
|
||||
<div class="item-text-primary">导入便签</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 12px; background-color: var(--background-card)">
|
||||
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
关于
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border)">
|
||||
<div style="font-size: 16px; color: var(--text-primary)">版本</div>
|
||||
<div style="font-size: 15px; color: var(--text-tertiary)">1.0.0</div>
|
||||
<div class="settings-item settings-item-border">
|
||||
<div class="item-text-primary">版本</div>
|
||||
<div class="item-text-tertiary">1.0.0</div>
|
||||
</div>
|
||||
<div button @click="handlePrivacyPolicy" style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
|
||||
<div style="font-size: 16px; color: var(--text-primary)">隐私政策</div>
|
||||
<div button @click="handlePrivacyPolicy" class="settings-item settings-item-clickable settings-item-border">
|
||||
<div class="item-text-primary">隐私政策</div>
|
||||
</div>
|
||||
<div button @click="handleTermsOfService" style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; cursor: pointer">
|
||||
<div style="font-size: 16px; color: var(--text-primary)">服务条款</div>
|
||||
<div button @click="handleTermsOfService" class="settings-item settings-item-clickable">
|
||||
<div class="item-text-primary">服务条款</div>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -139,4 +139,65 @@ const handleBackPress = () => {
|
||||
};
|
||||
|
||||
const settings = computed(() => store.settings);
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.settings-content {
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
margin-bottom: 12px;
|
||||
background-color: var(--background-card);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
background-color: var(--background-secondary);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-tertiary);
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
.settings-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: var(--background-card);
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.settings-item-border {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.settings-item-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item-text-primary {
|
||||
font-size: 16px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.item-text-tertiary {
|
||||
font-size: 15px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--text-primary);
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.settings-item-clickable .item-text-primary {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.settings-item-clickable {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user