Files
SmartisanNote.Remake/src/pages/NoteListPage.vue
User 1cdc748b32 新增 便签删除功能;
优化便签列表页布局;
2025-10-13 15:36:46 +08:00

382 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<ion-page>
<div class="container">
<ion-content class="content">
<Header
:title="headerTitle"
:onAction="handleHeaderAction"
actionIcon="create"
leftType="settings"
:onLeftAction="handleSettingsPress"
:onFolderToggle="handleFolderToggle"
:isFolderExpanded="isFolderExpanded"
:onTitlePress="handleFolderToggle"
slot="fixed" />
<!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
<div v-if="isFolderExpanded" class="folder-list">
<FolderManage
:allCount="allNotesCount"
:starredCount="starredNotesCount"
:trashCount="trashNotesCount"
:archiveCount="0"
:selectedFolder="currentFolder"
:onAllClick="handleAllNotesClick"
:onStarredClick="handleStarredNotesClick"
:onTrashClick="handleTrashNotesClick" />
</div>
<!-- 点击外部区域收起文件夹列表的覆盖层 -->
<div v-if="isFolderExpanded" @click="() => setIsFolderExpanded(false)" class="folder-overlay"></div>
<div class="search-container">
<SearchBar v-model="searchQuery" @search="handleSearch" @clear="handleClearSearch" @focus="handleSearchFocus" @blur="handleSearchBlur" />
</div>
<div class="notes-container">
<div v-for="note in filteredAndSortedNotes" :key="note.id" class="note-item">
<NoteItem
:title="note.title"
:content="note.content"
:date="formatDate(note.updatedAt)"
:isStarred="note.isStarred"
:isTop="note.isTop || false"
:hasImage="note.hasImage || false"
:onPress="() => handleNotePress(note.id)"
:onStarToggle="() => handleStarToggle(note.id)"
:onTopToggle="() => handleTopToggle(note.id)"
:onDelete="() => confirmDeleteNote(note.id)" />
</div>
</div>
</ion-content>
</div>
</ion-page>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '../stores/useAppStore'
import NoteItem from '../components/NoteItem.vue'
import Header from '../components/Header.vue'
import FolderManage from '../components/FolderManage.vue'
import SearchBar from '../components/SearchBar.vue'
import { formatNoteListDate } from '../utils/dateUtils'
import { IonContent, IonPage } from '@ionic/vue'
const store = useAppStore()
const router = useRouter()
// 页面挂载时加载初始数据
onMounted(() => {
// 检查URL参数是否包含mock数据加载指令用于开发和演示
const urlParams = new URLSearchParams(window.location.search)
if (urlParams.get('mock') === 'true') {
// 加载预设的模拟数据
store.loadMockData()
} else {
// 从localStorage加载用户数据
store.loadData()
}
})
const searchQuery = ref('')
const sortBy = ref('date') // 排序方式:'date'(按日期)、'title'(按标题)、'starred'(按星标)
const isFolderExpanded = ref(false) // 文件夹列表是否展开
const currentFolder = ref('all') // 当前选中的文件夹,默认是"全部便签"
const noteToDelete = ref(null)
// 计算加星便签数量(未删除的)
const starredNotesCount = computed(() => {
return store.notes.filter(note => note.isStarred && !note.isDeleted).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 !note.isDeleted
case 'starred':
// 加星便签中只显示未删除的加星便签
return note.isStarred && !note.isDeleted
case 'trash':
// 回收站中只显示已删除的便签
return note.isDeleted
default:
// 自定义文件夹中不显示已删除的便签
return note.folderId === currentFolder.value && !note.isDeleted
}
})
})
// 过滤并排序便签列表
// 首先按置顶状态排序,置顶的便签排在前面
// 然后根据sortBy的值进行二次排序
const filteredAndSortedNotes = computed(() => {
return [...filteredNotes.value].sort((a, b) => {
// 置顶的便签排在前面
if (a.isTop && !b.isTop) return -1
if (!a.isTop && b.isTop) return 1
// 根据排序方式排序
switch (sortBy.value) {
case 'title':
// 按标题字母顺序排序
return a.title.localeCompare(b.title)
case 'starred':
// 按星标状态排序,加星的便签排在前面
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0)
case 'date':
default:
// 按更新时间倒序排列(最新的在前)
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
}
})
})
// 计算头部标题
const headerTitle = computed(() => {
switch (currentFolder.value) {
case 'all':
return '全部便签'
case 'starred':
return '加星便签'
case 'trash':
return '回收站'
default:
return '全部便签'
}
})
// 计算全部便签数量(未删除的)
const allNotesCount = computed(() => {
return store.notes.filter(note => !note.isDeleted).length
})
const handleNotePress = noteId => {
// 使用vue-router导航到编辑页面
router.push(`/editor/${noteId}`)
}
const handleAddNote = () => {
// 使用vue-router导航到新建便签页面
router.push('/editor')
}
// 处理Header组件的操作按钮点击事件
const handleHeaderAction = actionType => {
if (actionType === 'create') {
handleAddNote()
}
}
const handleStarToggle = async noteId => {
const note = store.notes.find(n => n.id === noteId)
if (note) {
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) {
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 = async noteId => {
noteToDelete.value = noteId
if (noteToDelete.value) {
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)
}
}
}
// 处理排序方式切换
// 循环切换排序选项:按日期 -> 按标题 -> 按星标 -> 按日期...
const handleSort = () => {
const sortOptions = ['date', 'title', 'starred']
const currentIndex = sortOptions.indexOf(sortBy.value)
const nextIndex = (currentIndex + 1) % sortOptions.length
sortBy.value = sortOptions[nextIndex]
console.log('当前排序方式:', sortOptions[nextIndex])
}
const handleAllNotesClick = () => {
setCurrentFolder('all')
setIsFolderExpanded(false)
}
const handleStarredNotesClick = () => {
setCurrentFolder('starred')
setIsFolderExpanded(false)
}
const handleTrashNotesClick = () => {
setCurrentFolder('trash')
setIsFolderExpanded(false)
}
const handleFolderPress = () => {
// 使用vue-router导航到文件夹页面
router.push('/folders')
}
const handleSettingsPress = () => {
// 使用vue-router导航到设置页面
router.push('/settings')
}
const handleFolderToggle = () => {
// 在实际应用中,这里会触发文件夹列表的展开/收起
isFolderExpanded.value = !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)
}
}
// 防抖搜索处理函数延迟300ms执行搜索
const debouncedHandleSearch = debounceSearch(query => {
handleSearch(query)
}, 300)
// 改进的日期格式化函数
const formatDate = dateString => {
return formatNoteListDate(dateString)
}
const setCurrentFolder = folder => {
currentFolder.value = folder
}
const setIsFolderExpanded = expanded => {
isFolderExpanded.value = expanded
}
const setSearchQuery = query => {
searchQuery.value = query
}
const notes = computed(() => store.notes)
</script>
<style lang="less" scoped>
.container {
width: 100vw;
height: 100vh;
background: url(/assets/icons/drawable-xxhdpi/note_background.png);
background-size: cover;
}
.folder-list {
position: absolute;
top: 3.125rem;
left: 10%;
right: 10%;
z-index: 1000;
background-color: var(--background-card);
border-radius: 0.5rem;
box-shadow: 0 0.125rem 0.25rem 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;
}
.content {
--background: transparent;
--padding-top: 4.5rem;
--padding-bottom: 2rem;
}
.search-container {
padding: 0.8rem 0.5rem;
}
.notes-container {
flex: 1;
}
.note-item {
margin: 0.6rem 0;
}
</style>